Commit 41620b53 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '229400-incident-state' into 'master'

Add incident state columns

See merge request gitlab-org/gitlab!37889
parents cda94de0 72d1779d
...@@ -11,13 +11,15 @@ import { ...@@ -11,13 +11,15 @@ import {
GlSearchBoxByType, GlSearchBoxByType,
GlIcon, GlIcon,
GlPagination, GlPagination,
GlTabs,
GlTab,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { debounce } from 'lodash'; import { debounce, trim } from 'lodash';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { mergeUrlParams, joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { mergeUrlParams, joinPaths, visitUrl } from '~/lib/utils/url_utility';
import getIncidents from '../graphql/queries/get_incidents.query.graphql'; import getIncidents from '../graphql/queries/get_incidents.query.graphql';
import { I18N, DEFAULT_PAGE_SIZE, INCIDENT_SEARCH_DELAY } from '../constants'; import { I18N, DEFAULT_PAGE_SIZE, INCIDENT_SEARCH_DELAY, INCIDENT_STATE_TABS } from '../constants';
const tdClass = const tdClass =
'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap'; 'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap';
...@@ -35,6 +37,7 @@ const initialPaginationState = { ...@@ -35,6 +37,7 @@ const initialPaginationState = {
export default { export default {
i18n: I18N, i18n: I18N,
stateTabs: INCIDENT_STATE_TABS,
fields: [ fields: [
{ {
key: 'title', key: 'title',
...@@ -67,6 +70,8 @@ export default { ...@@ -67,6 +70,8 @@ export default {
GlSearchBoxByType, GlSearchBoxByType,
GlIcon, GlIcon,
GlPagination, GlPagination,
GlTabs,
GlTab,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -78,6 +83,7 @@ export default { ...@@ -78,6 +83,7 @@ export default {
variables() { variables() {
return { return {
searchTerm: this.searchTerm, searchTerm: this.searchTerm,
state: this.stateFilter,
projectPath: this.projectPath, projectPath: this.projectPath,
labelNames: ['incident'], labelNames: ['incident'],
firstPageSize: this.pagination.firstPageSize, firstPageSize: this.pagination.firstPageSize,
...@@ -105,6 +111,7 @@ export default { ...@@ -105,6 +111,7 @@ export default {
searchTerm: '', searchTerm: '',
pagination: initialPaginationState, pagination: initialPaginationState,
incidents: {}, incidents: {},
stateFilter: '',
}; };
}, },
computed: { computed: {
...@@ -138,14 +145,17 @@ export default { ...@@ -138,14 +145,17 @@ export default {
return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath); return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath);
}, },
}, },
watch: { methods: {
searchTerm: debounce(function debounceSearch(input) { onInputChange: debounce(function debounceSearch(input) {
if (input !== this.searchTerm) { const trimmedInput = trim(input);
this.searchTerm = input; if (trimmedInput !== this.searchTerm) {
this.searchTerm = trimmedInput;
} }
}, INCIDENT_SEARCH_DELAY), }, INCIDENT_SEARCH_DELAY),
filterIncidentsByState(tabIndex) {
const { filters } = this.$options.stateTabs[tabIndex];
this.stateFilter = filters;
}, },
methods: {
hasAssignees(assignees) { hasAssignees(assignees) {
return Boolean(assignees.nodes?.length); return Boolean(assignees.nodes?.length);
}, },
...@@ -183,9 +193,17 @@ export default { ...@@ -183,9 +193,17 @@ export default {
{{ $options.i18n.errorMsg }} {{ $options.i18n.errorMsg }}
</gl-alert> </gl-alert>
<div class="gl-display-flex gl-justify-content-end"> <div class="incident-management-list-header gl-display-flex gl-justify-content-space-between">
<gl-tabs content-class="gl-p-0" @input="filterIncidentsByState">
<gl-tab v-for="tab in $options.stateTabs" :key="tab.state" :data-testid="tab.state">
<template #title>
<span>{{ tab.title }}</span>
</template>
</gl-tab>
</gl-tabs>
<gl-button <gl-button
class="gl-mt-3 gl-mb-3 create-incident-button" class="gl-my-3 create-incident-button"
data-testid="createIncidentBtn" data-testid="createIncidentBtn"
:loading="redirecting" :loading="redirecting"
:disabled="redirecting" :disabled="redirecting"
...@@ -200,9 +218,10 @@ export default { ...@@ -200,9 +218,10 @@ export default {
<div class="gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100"> <div class="gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100">
<gl-search-box-by-type <gl-search-box-by-type
v-model.trim="searchTerm" :value="searchTerm"
class="gl-bg-white" class="gl-bg-white"
:placeholder="$options.i18n.searchPlaceholder" :placeholder="$options.i18n.searchPlaceholder"
@input="onInputChange"
/> />
</div> </div>
...@@ -221,7 +240,7 @@ export default { ...@@ -221,7 +240,7 @@ export default {
@row-clicked="navigateToIncidentDetails" @row-clicked="navigateToIncidentDetails"
> >
<template #cell(title)="{ item }"> <template #cell(title)="{ item }">
<div class="gl-display-flex gl-justify-content-center"> <div class="gl-display-sm-flex gl-align-items-center">
<div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div> <div class="gl-max-w-full text-truncate" :title="item.title">{{ item.title }}</div>
<gl-icon <gl-icon
v-if="item.state === 'closed'" v-if="item.state === 'closed'"
......
...@@ -8,5 +8,23 @@ export const I18N = { ...@@ -8,5 +8,23 @@ export const I18N = {
searchPlaceholder: __('Search or filter results...'), searchPlaceholder: __('Search or filter results...'),
}; };
export const INCIDENT_STATE_TABS = [
{
title: s__('IncidentManagement|Open'),
state: 'OPENED',
filters: 'opened',
},
{
title: s__('IncidentManagement|Closed'),
state: 'CLOSED',
filters: 'closed',
},
{
title: s__('IncidentManagement|All incidents'),
state: 'ALL',
filters: 'all',
},
];
export const INCIDENT_SEARCH_DELAY = 300; export const INCIDENT_SEARCH_DELAY = 300;
export const DEFAULT_PAGE_SIZE = 10; export const DEFAULT_PAGE_SIZE = 10;
...@@ -90,6 +90,10 @@ ...@@ -90,6 +90,10 @@
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
.incident-management-list-header {
flex-direction: column-reverse;
};
.create-incident-button { .create-incident-button {
@include gl-w-full; @include gl-w-full;
} }
......
...@@ -8,5 +8,6 @@ module Types ...@@ -8,5 +8,6 @@ module Types
value 'opened' value 'opened'
value 'closed' value 'closed'
value 'locked' value 'locked'
value 'all'
end end
end end
---
title: Add incident state columns
merge_request: 37889
author:
type: other
...@@ -5861,6 +5861,7 @@ type InstanceSecurityDashboard { ...@@ -5861,6 +5861,7 @@ type InstanceSecurityDashboard {
State of a GitLab issue or merge request State of a GitLab issue or merge request
""" """
enum IssuableState { enum IssuableState {
all
closed closed
locked locked
opened opened
...@@ -6562,6 +6563,7 @@ enum IssueSort { ...@@ -6562,6 +6563,7 @@ enum IssueSort {
State of a GitLab issue State of a GitLab issue
""" """
enum IssueState { enum IssueState {
all
closed closed
locked locked
opened opened
...@@ -7987,6 +7989,7 @@ type MergeRequestSetWipPayload { ...@@ -7987,6 +7989,7 @@ type MergeRequestSetWipPayload {
State of a GitLab merge request State of a GitLab merge request
""" """
enum MergeRequestState { enum MergeRequestState {
all
closed closed
locked locked
merged merged
......
...@@ -16186,6 +16186,12 @@ ...@@ -16186,6 +16186,12 @@
"description": null, "description": null,
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"possibleTypes": null "possibleTypes": null
...@@ -18108,6 +18114,12 @@ ...@@ -18108,6 +18114,12 @@
"description": null, "description": null,
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"possibleTypes": null "possibleTypes": null
...@@ -22394,6 +22406,12 @@ ...@@ -22394,6 +22406,12 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "merged", "name": "merged",
"description": null, "description": null,
...@@ -12713,9 +12713,15 @@ msgstr "" ...@@ -12713,9 +12713,15 @@ msgstr ""
msgid "Incident Management Limits" msgid "Incident Management Limits"
msgstr "" msgstr ""
msgid "IncidentManagement|All incidents"
msgstr ""
msgid "IncidentManagement|Assignees" msgid "IncidentManagement|Assignees"
msgstr "" msgstr ""
msgid "IncidentManagement|Closed"
msgstr ""
msgid "IncidentManagement|Create incident" msgid "IncidentManagement|Create incident"
msgstr "" msgstr ""
...@@ -12731,6 +12737,9 @@ msgstr "" ...@@ -12731,6 +12737,9 @@ msgstr ""
msgid "IncidentManagement|No incidents to display." msgid "IncidentManagement|No incidents to display."
msgstr "" msgstr ""
msgid "IncidentManagement|Open"
msgstr ""
msgid "IncidentManagement|There was an error displaying the incidents." msgid "IncidentManagement|There was an error displaying the incidents."
msgstr "" msgstr ""
......
...@@ -6,11 +6,12 @@ import { ...@@ -6,11 +6,12 @@ import {
GlAvatar, GlAvatar,
GlPagination, GlPagination,
GlSearchBoxByType, GlSearchBoxByType,
GlTab,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import IncidentsList from '~/incidents/components/incidents_list.vue'; import IncidentsList from '~/incidents/components/incidents_list.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { I18N } from '~/incidents/constants'; import { I18N, INCIDENT_STATE_TABS } from '~/incidents/constants';
import mockIncidents from '../mocks/incidents.json'; import mockIncidents from '../mocks/incidents.json';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
...@@ -34,6 +35,7 @@ describe('Incidents List', () => { ...@@ -34,6 +35,7 @@ describe('Incidents List', () => {
const findSearch = () => wrapper.find(GlSearchBoxByType); const findSearch = () => wrapper.find(GlSearchBoxByType);
const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']"); const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']");
const findPagination = () => wrapper.find(GlPagination); const findPagination = () => wrapper.find(GlPagination);
const findStatusFilterTabs = () => wrapper.findAll(GlTab);
function mountComponent({ data = { incidents: [] }, loading = false }) { function mountComponent({ data = { incidents: [] }, loading = false }) {
wrapper = mount(IncidentsList, { wrapper = mount(IncidentsList, {
...@@ -280,5 +282,25 @@ describe('Incidents List', () => { ...@@ -280,5 +282,25 @@ describe('Incidents List', () => {
expect(wrapper.vm.$data.searchTerm).toBe(SEARCH_TERM); expect(wrapper.vm.$data.searchTerm).toBe(SEARCH_TERM);
}); });
}); });
describe('State Filter Tabs', () => {
beforeEach(() => {
mountComponent({
data: { incidents: mockIncidents },
loading: false,
stubs: {
GlTab: true,
},
});
});
it('should display filter tabs', () => {
const tabs = findStatusFilterTabs().wrappers;
tabs.forEach((tab, i) => {
expect(tab.attributes('data-testid')).toContain(INCIDENT_STATE_TABS[i].state);
});
});
});
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment