Commit 402864d0 authored by Phil Hughes's avatar Phil Hughes

Merge branch '229636-empty-incidents-list' into 'master'

Empty state for incidents list

See merge request gitlab-org/gitlab!39718
parents 37feaa44 ff9c784c
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge, GlBadge,
GlEmptyState,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
...@@ -80,6 +81,7 @@ export default { ...@@ -80,6 +81,7 @@ export default {
GlTab, GlTab,
PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'), PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'),
GlBadge, GlBadge,
GlEmptyState,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -91,6 +93,7 @@ export default { ...@@ -91,6 +93,7 @@ export default {
'incidentType', 'incidentType',
'issuePath', 'issuePath',
'publishedAvailable', 'publishedAvailable',
'emptyListSvgPath',
], ],
apollo: { apollo: {
incidents: { incidents: {
...@@ -149,7 +152,7 @@ export default { ...@@ -149,7 +152,7 @@ export default {
}, },
computed: { computed: {
showErrorMsg() { showErrorMsg() {
return this.errored && !this.isErrorAlertDismissed && this.incidentsCount?.all === 0; return this.errored && !this.isErrorAlertDismissed;
}, },
loading() { loading() {
return this.$apollo.queries.incidents.loading; return this.$apollo.queries.incidents.loading;
...@@ -202,6 +205,9 @@ export default { ...@@ -202,6 +205,9 @@ export default {
] ]
: this.$options.fields; : this.$options.fields;
}, },
isEmpty() {
return !this.incidents.list?.length;
},
}, },
methods: { methods: {
onInputChange: debounce(function debounceSearch(input) { onInputChange: debounce(function debounceSearch(input) {
...@@ -273,6 +279,7 @@ export default { ...@@ -273,6 +279,7 @@ export default {
</gl-tabs> </gl-tabs>
<gl-button <gl-button
v-if="!isEmpty"
class="gl-my-3 gl-mr-5 create-incident-button" class="gl-my-3 gl-mr-5 create-incident-button"
data-testid="createIncidentBtn" data-testid="createIncidentBtn"
data-qa-selector="create_incident_button" data-qa-selector="create_incident_button"
...@@ -373,7 +380,17 @@ export default { ...@@ -373,7 +380,17 @@ export default {
</template> </template>
<template #empty> <template #empty>
<gl-empty-state
v-if="!errored"
:title="$options.i18n.emptyState.title"
:svg-path="emptyListSvgPath"
:description="$options.i18n.emptyState.description"
:primary-button-link="newIncidentPath"
:primary-button-text="$options.i18n.createIncidentBtnLabel"
/>
<span v-else>
{{ $options.i18n.noIncidents }} {{ $options.i18n.noIncidents }}
</span>
</template> </template>
</gl-table> </gl-table>
......
...@@ -7,6 +7,12 @@ export const I18N = { ...@@ -7,6 +7,12 @@ export const I18N = {
createIncidentBtnLabel: s__('IncidentManagement|Create incident'), createIncidentBtnLabel: s__('IncidentManagement|Create incident'),
unPublished: s__('IncidentManagement|Unpublished'), unPublished: s__('IncidentManagement|Unpublished'),
searchPlaceholder: __('Search results…'), searchPlaceholder: __('Search results…'),
emptyState: {
title: s__('IncidentManagement|Display your incidents in a dedicated view'),
description: s__(
'IncidentManagement|All alerts promoted to incidents will automatically be displayed within the list. You can also create a new incident using the button below.',
),
},
}; };
export const INCIDENT_STATUS_TABS = [ export const INCIDENT_STATUS_TABS = [
......
...@@ -15,6 +15,7 @@ export default () => { ...@@ -15,6 +15,7 @@ export default () => {
incidentType, incidentType,
issuePath, issuePath,
publishedAvailable, publishedAvailable,
emptyListSvgPath,
} = domEl.dataset; } = domEl.dataset;
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
...@@ -30,6 +31,7 @@ export default () => { ...@@ -30,6 +31,7 @@ export default () => {
newIssuePath, newIssuePath,
issuePath, issuePath,
publishedAvailable, publishedAvailable,
emptyListSvgPath,
}, },
apolloProvider, apolloProvider,
components: { components: {
......
...@@ -7,7 +7,8 @@ module Projects::IncidentsHelper ...@@ -7,7 +7,8 @@ module Projects::IncidentsHelper
'new-issue-path' => new_project_issue_path(project), 'new-issue-path' => new_project_issue_path(project),
'incident-template-name' => 'incident', 'incident-template-name' => 'incident',
'incident-type' => 'incident', 'incident-type' => 'incident',
'issue-path' => project_issues_path(project) 'issue-path' => project_issues_path(project),
'empty-list-svg-path' => image_path('illustrations/incident-empty-state.svg')
} }
end end
end end
......
---
title: Empty State for the Incident list
merge_request: 39718
author:
type: added
...@@ -17,7 +17,8 @@ RSpec.describe Projects::IncidentsHelper do ...@@ -17,7 +17,8 @@ RSpec.describe Projects::IncidentsHelper do
'new-issue-path' => new_issue_path, 'new-issue-path' => new_issue_path,
'incident-template-name' => 'incident', 'incident-template-name' => 'incident',
'incident-type' => 'incident', 'incident-type' => 'incident',
'issue-path' => issue_path 'issue-path' => issue_path,
'empty-list-svg-path' => match_asset_path('/assets/illustrations/incident-empty-state.svg')
} }
end end
...@@ -30,13 +31,13 @@ RSpec.describe Projects::IncidentsHelper do ...@@ -30,13 +31,13 @@ RSpec.describe Projects::IncidentsHelper do
context 'when status page feature is available' do context 'when status page feature is available' do
let(:status_page_feature_available) { true } let(:status_page_feature_available) { true }
it { is_expected.to eq(expected_incidents_data.merge('published-available' => 'true')) } it { is_expected.to match(expected_incidents_data.merge('published-available' => 'true')) }
end end
context 'when status page issue is not available' do context 'when status page issue is not available' do
let(:status_page_feature_available) { false } let(:status_page_feature_available) { false }
it { is_expected.to eq(expected_incidents_data) } it { is_expected.to match(expected_incidents_data) }
end end
end end
end end
...@@ -12959,6 +12959,9 @@ msgstr "" ...@@ -12959,6 +12959,9 @@ msgstr ""
msgid "IncidentManagement|All" msgid "IncidentManagement|All"
msgstr "" msgstr ""
msgid "IncidentManagement|All alerts promoted to incidents will automatically be displayed within the list. You can also create a new incident using the button below."
msgstr ""
msgid "IncidentManagement|Assignees" msgid "IncidentManagement|Assignees"
msgstr "" msgstr ""
...@@ -12971,6 +12974,9 @@ msgstr "" ...@@ -12971,6 +12974,9 @@ msgstr ""
msgid "IncidentManagement|Date created" msgid "IncidentManagement|Date created"
msgstr "" msgstr ""
msgid "IncidentManagement|Display your incidents in a dedicated view"
msgstr ""
msgid "IncidentManagement|Incident" msgid "IncidentManagement|Incident"
msgstr "" msgstr ""
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
GlTab, GlTab,
GlTabs, GlTabs,
GlBadge, GlBadge,
GlEmptyState,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility'; import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
import IncidentsList from '~/incidents/components/incidents_list.vue'; import IncidentsList from '~/incidents/components/incidents_list.vue';
...@@ -25,6 +26,7 @@ jest.mock('~/lib/utils/url_utility', () => ({ ...@@ -25,6 +26,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('Incidents List', () => { describe('Incidents List', () => {
let wrapper; let wrapper;
const newIssuePath = 'namespace/project/-/issues/new'; const newIssuePath = 'namespace/project/-/issues/new';
const emptyListSvgPath = '/assets/empty.svg';
const incidentTemplateName = 'incident'; const incidentTemplateName = 'incident';
const incidentType = 'incident'; const incidentType = 'incident';
const incidentsCount = { const incidentsCount = {
...@@ -48,6 +50,7 @@ describe('Incidents List', () => { ...@@ -48,6 +50,7 @@ describe('Incidents List', () => {
const findStatusFilterTabs = () => wrapper.findAll(GlTab); const findStatusFilterTabs = () => wrapper.findAll(GlTab);
const findStatusFilterBadge = () => wrapper.findAll(GlBadge); const findStatusFilterBadge = () => wrapper.findAll(GlBadge);
const findStatusTabs = () => wrapper.find(GlTabs); const findStatusTabs = () => wrapper.find(GlTabs);
const findEmptyState = () => wrapper.find(GlEmptyState);
function mountComponent({ data = { incidents: [], incidentsCount: {} }, loading = false }) { function mountComponent({ data = { incidents: [], incidentsCount: {} }, loading = false }) {
wrapper = mount(IncidentsList, { wrapper = mount(IncidentsList, {
...@@ -70,6 +73,7 @@ describe('Incidents List', () => { ...@@ -70,6 +73,7 @@ describe('Incidents List', () => {
incidentType, incidentType,
issuePath: '/project/isssues', issuePath: '/project/isssues',
publishedAvailable: true, publishedAvailable: true,
emptyListSvgPath,
}, },
stubs: { stubs: {
GlButton: true, GlButton: true,
...@@ -97,7 +101,7 @@ describe('Incidents List', () => { ...@@ -97,7 +101,7 @@ describe('Incidents List', () => {
data: { incidents: { list: [] }, incidentsCount: {} }, data: { incidents: { list: [] }, incidentsCount: {} },
loading: false, loading: false,
}); });
expect(findTable().text()).toContain(I18N.noIncidents); expect(findEmptyState().exists()).toBe(true);
}); });
it('shows error state', () => { it('shows error state', () => {
...@@ -164,7 +168,7 @@ describe('Incidents List', () => { ...@@ -164,7 +168,7 @@ describe('Incidents List', () => {
describe('Create Incident', () => { describe('Create Incident', () => {
beforeEach(() => { beforeEach(() => {
mountComponent({ mountComponent({
data: { incidents: { list: [] }, incidentsCount: {} }, data: { incidents: { list: mockIncidents }, incidentsCount: {} },
loading: false, loading: false,
}); });
}); });
......
...@@ -19,7 +19,8 @@ RSpec.describe Projects::IncidentsHelper do ...@@ -19,7 +19,8 @@ RSpec.describe Projects::IncidentsHelper do
'new-issue-path' => new_issue_path, 'new-issue-path' => new_issue_path,
'incident-template-name' => 'incident', 'incident-template-name' => 'incident',
'incident-type' => 'incident', 'incident-type' => 'incident',
'issue-path' => issue_path 'issue-path' => issue_path,
'empty-list-svg-path' => match_asset_path('/assets/illustrations/incident-empty-state.svg')
) )
end end
end end
......
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