Commit 72d1779d authored by David O'Regan's avatar David O'Regan Committed by Natalia Tepluhina

Add incident state columns

We add incident state
columns to allow users
to filter via state
parent cb4db18b
......@@ -11,13 +11,15 @@ import {
GlSearchBoxByType,
GlIcon,
GlPagination,
GlTabs,
GlTab,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { debounce, trim } from 'lodash';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { s__ } from '~/locale';
import { mergeUrlParams, joinPaths, visitUrl } from '~/lib/utils/url_utility';
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 =
'table-col gl-display-flex d-md-table-cell gl-align-items-center gl-white-space-nowrap';
......@@ -35,6 +37,7 @@ const initialPaginationState = {
export default {
i18n: I18N,
stateTabs: INCIDENT_STATE_TABS,
fields: [
{
key: 'title',
......@@ -67,6 +70,8 @@ export default {
GlSearchBoxByType,
GlIcon,
GlPagination,
GlTabs,
GlTab,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -78,6 +83,7 @@ export default {
variables() {
return {
searchTerm: this.searchTerm,
state: this.stateFilter,
projectPath: this.projectPath,
labelNames: ['incident'],
firstPageSize: this.pagination.firstPageSize,
......@@ -105,6 +111,7 @@ export default {
searchTerm: '',
pagination: initialPaginationState,
incidents: {},
stateFilter: '',
};
},
computed: {
......@@ -138,14 +145,17 @@ export default {
return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath);
},
},
watch: {
searchTerm: debounce(function debounceSearch(input) {
if (input !== this.searchTerm) {
this.searchTerm = input;
methods: {
onInputChange: debounce(function debounceSearch(input) {
const trimmedInput = trim(input);
if (trimmedInput !== this.searchTerm) {
this.searchTerm = trimmedInput;
}
}, INCIDENT_SEARCH_DELAY),
filterIncidentsByState(tabIndex) {
const { filters } = this.$options.stateTabs[tabIndex];
this.stateFilter = filters;
},
methods: {
hasAssignees(assignees) {
return Boolean(assignees.nodes?.length);
},
......@@ -183,9 +193,17 @@ export default {
{{ $options.i18n.errorMsg }}
</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
class="gl-mt-3 gl-mb-3 create-incident-button"
class="gl-my-3 create-incident-button"
data-testid="createIncidentBtn"
:loading="redirecting"
:disabled="redirecting"
......@@ -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">
<gl-search-box-by-type
v-model.trim="searchTerm"
:value="searchTerm"
class="gl-bg-white"
:placeholder="$options.i18n.searchPlaceholder"
@input="onInputChange"
/>
</div>
......@@ -221,7 +240,7 @@ export default {
@row-clicked="navigateToIncidentDetails"
>
<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>
<gl-icon
v-if="item.state === 'closed'"
......
......@@ -8,5 +8,23 @@ export const I18N = {
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 DEFAULT_PAGE_SIZE = 10;
......@@ -90,6 +90,10 @@
}
@include media-breakpoint-down(xs) {
.incident-management-list-header {
flex-direction: column-reverse;
};
.create-incident-button {
@include gl-w-full;
}
......
......@@ -8,5 +8,6 @@ module Types
value 'opened'
value 'closed'
value 'locked'
value 'all'
end
end
---
title: Add incident state columns
merge_request: 37889
author:
type: other
......@@ -5856,6 +5856,7 @@ type InstanceSecurityDashboard {
State of a GitLab issue or merge request
"""
enum IssuableState {
all
closed
locked
opened
......@@ -6557,6 +6558,7 @@ enum IssueSort {
State of a GitLab issue
"""
enum IssueState {
all
closed
locked
opened
......@@ -7982,6 +7984,7 @@ type MergeRequestSetWipPayload {
State of a GitLab merge request
"""
enum MergeRequestState {
all
closed
locked
merged
......
......@@ -16168,6 +16168,12 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
......@@ -18090,6 +18096,12 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
......@@ -22376,6 +22388,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "merged",
"description": null,
......@@ -12710,9 +12710,15 @@ msgstr ""
msgid "Incident Management Limits"
msgstr ""
msgid "IncidentManagement|All incidents"
msgstr ""
msgid "IncidentManagement|Assignees"
msgstr ""
msgid "IncidentManagement|Closed"
msgstr ""
msgid "IncidentManagement|Create incident"
msgstr ""
......@@ -12728,6 +12734,9 @@ msgstr ""
msgid "IncidentManagement|No incidents to display."
msgstr ""
msgid "IncidentManagement|Open"
msgstr ""
msgid "IncidentManagement|There was an error displaying the incidents."
msgstr ""
......
......@@ -6,11 +6,12 @@ import {
GlAvatar,
GlPagination,
GlSearchBoxByType,
GlTab,
} from '@gitlab/ui';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import IncidentsList from '~/incidents/components/incidents_list.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';
jest.mock('~/lib/utils/url_utility', () => ({
......@@ -34,6 +35,7 @@ describe('Incidents List', () => {
const findSearch = () => wrapper.find(GlSearchBoxByType);
const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']");
const findPagination = () => wrapper.find(GlPagination);
const findStatusFilterTabs = () => wrapper.findAll(GlTab);
function mountComponent({ data = { incidents: [] }, loading = false }) {
wrapper = mount(IncidentsList, {
......@@ -280,5 +282,25 @@ describe('Incidents List', () => {
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