Commit d8dfa19f authored by Phil Hughes's avatar Phil Hughes

Merge branch '213890-alerts-loading-state' into 'master'

Alert list loading & error states

See merge request gitlab-org/gitlab!30315
parents cf4af500 b8d95a9c
<script> <script>
import { mapState } from 'vuex';
import { GlEmptyState, GlButton, GlLoadingIcon, GlTable, GlAlert } from '@gitlab/ui'; import { GlEmptyState, GlButton, GlLoadingIcon, GlTable, GlAlert } from '@gitlab/ui';
import { __ } from '~/locale'; import { s__ } from '~/locale';
import getAlerts from '../graphql/queries/getAlerts.query.graphql';
const tdClass = 'table-col d-flex'; const tdClass = 'table-col d-flex';
export default { export default {
i18n: {
noAlertsMsg: s__(
"AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page.",
),
errorMsg: s__(
"AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear.",
),
},
fields: [ fields: [
{ {
key: 'severity', key: 'severity',
label: __('Severity'), label: s__('AlertManagement|Severity'),
tdClass, tdClass,
}, },
{ {
key: 'start_time', key: 'start_time',
label: __('Start Time'), label: s__('AlertManagement|Start Time'),
tdClass, tdClass,
}, },
{ {
key: 'end_time', key: 'end_time',
label: __('End Time'), label: s__('AlertManagement|End Time'),
tdClass, tdClass,
}, },
{ {
key: 'alert', key: 'alert',
label: __('Alert'), label: s__('AlertManagement|Alert'),
thClass: 'w-30p', thClass: 'w-30p',
tdClass, tdClass,
}, },
{ {
key: 'events', key: 'events',
label: __('Events'), label: s__('AlertManagement|Events'),
tdClass, tdClass,
}, },
{ {
key: 'status', key: 'status',
label: __('Status'), label: s__('AlertManagement|Status'),
tdClass, tdClass,
}, },
], ],
...@@ -66,15 +74,36 @@ export default { ...@@ -66,15 +74,36 @@ export default {
required: true, required: true,
}, },
}, },
apollo: {
alerts: {
query: getAlerts,
variables() {
return {
projectPath: this.indexPath,
};
},
error() {
this.errored = true;
},
},
},
data() { data() {
return { return {
alerts: null,
errored: false,
isAlertDismissed: false, isAlertDismissed: false,
isErrorAlertDismissed: false,
}; };
}, },
computed: { computed: {
...mapState('list', ['alerts', 'loading']),
showNoAlertsMsg() { showNoAlertsMsg() {
return !this.alerts.length && !this.isAlertDismissed; return !this.errored && !this.loading && !this.alerts?.length && !this.isAlertDismissed;
},
showErrorMsg() {
return this.errored && !this.isErrorAlertDismissed;
},
loading() {
return this.$apollo.queries.alerts.loading;
}, },
}, },
}; };
...@@ -84,46 +113,49 @@ export default { ...@@ -84,46 +113,49 @@ export default {
<div> <div>
<div v-if="alertManagementEnabled" class="alert-management-list"> <div v-if="alertManagementEnabled" class="alert-management-list">
<gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true"> <gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true">
{{ {{ $options.i18n.noAlertsMsg }}
__( </gl-alert>
`No alerts available to display. If you think you're seeing this message in error, refresh the page.`, <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true">
) {{ $options.i18n.errorMsg }}
}}
</gl-alert> </gl-alert>
<div v-if="loading" class="py-3">
<gl-loading-icon size="md" />
</div>
<gl-table <gl-table
class="mt-3" class="mt-3"
:items="alerts" :items="alerts"
:fields="$options.fields" :fields="$options.fields"
:show-empty="true" :show-empty="true"
:busy="loading"
fixed fixed
stacked="sm" stacked="sm"
tbody-tr-class="table-row mb-4" tbody-tr-class="table-row mb-4"
> >
<template #empty> <template #empty>
{{ __('No alerts to display.') }} {{ s__('AlertManagement|No alerts to display.') }}
</template>
<template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template> </template>
</gl-table> </gl-table>
</div> </div>
<template v-else> <template v-else>
<gl-empty-state :title="__('Surface alerts in GitLab')" :svg-path="emptyAlertSvgPath"> <gl-empty-state
:title="s__('AlertManagement|Surface alerts in GitLab')"
:svg-path="emptyAlertSvgPath"
>
<template #description> <template #description>
<div class="d-block"> <div class="d-block">
<span>{{ <span>{{
__( s__(
'Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.', 'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
) )
}}</span> }}</span>
<a href="/help/user/project/operations/alert_management.html"> <a href="/help/user/project/operations/alert_management.html">
{{ __('More information') }} {{ s__('AlertManagement|More information') }}
</a> </a>
</div> </div>
<div class="d-block center pt-4"> <div class="d-block center pt-4">
<gl-button category="primary" variant="success" :href="enableAlertManagementPath"> <gl-button category="primary" variant="success" :href="enableAlertManagementPath">
{{ __('Authorize external service') }} {{ s__('AlertManagement|Authorize external service') }}
</gl-button> </gl-button>
</div> </div>
</template> </template>
......
fragment AlertListItem on Alert {
iid
title
severity
status
started_at
ended_at
event_count
}
#import "../fragments/listItem.fragment.graphql"
query getAlerts(
$projectPath: ID!
) {
project(fullPath: $projectPath) {
alerts {
...AlertListItem
}
}
}
import Vue from 'vue'; import Vue from 'vue';
import store from './store'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import AlertManagementList from './components/alert_management_list.vue'; import AlertManagementList from './components/alert_management_list.vue';
Vue.use(VueApollo);
export default () => { export default () => {
const selector = '#js-alert_management'; const selector = '#js-alert_management';
const domEl = document.querySelector(selector); const domEl = document.querySelector(selector);
const { indexPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset; const { indexPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({ return new Vue({
el: selector, el: selector,
apolloProvider,
components: { components: {
AlertManagementList, AlertManagementList,
}, },
store,
render(createElement) { render(createElement) {
return createElement('alert-management-list', { return createElement('alert-management-list', {
props: { props: {
......
import Vue from 'vue';
import Vuex from 'vuex';
import * as listActions from './list/actions';
import listMutations from './list/mutations';
import listState from './list/state';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
list: {
namespaced: true,
state: listState(),
actions: listActions,
mutations: listMutations,
},
},
});
export default createStore();
import * as types from './mutation_types';
export const setAlerts = ({ commit }, alerts) => {
commit(types.SET_ALERTS, alerts);
};
export const setLoading = ({ commit }, loading) => {
commit(types.SET_LOADING, loading);
};
export const SET_ALERTS = 'SET_ALERTS';
export const SET_LOADING = 'SET_LOADING';
import * as types from './mutation_types';
export default {
[types.SET_ALERTS](state, alerts) {
state.alerts = alerts;
},
[types.SET_LOADING](state, loading) {
state.loading = loading;
},
};
export default () => ({
alerts: [],
loading: false,
});
---
title: Alerts list loading & error state
merge_request: 30315
author:
type: added
...@@ -1683,6 +1683,45 @@ msgid_plural "Alerts" ...@@ -1683,6 +1683,45 @@ msgid_plural "Alerts"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "AlertManagement|Alert"
msgstr ""
msgid "AlertManagement|Authorize external service"
msgstr ""
msgid "AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
msgstr ""
msgid "AlertManagement|End Time"
msgstr ""
msgid "AlertManagement|Events"
msgstr ""
msgid "AlertManagement|More information"
msgstr ""
msgid "AlertManagement|No alerts available to display. If you think you're seeing this message in error, refresh the page."
msgstr ""
msgid "AlertManagement|No alerts to display."
msgstr ""
msgid "AlertManagement|Severity"
msgstr ""
msgid "AlertManagement|Start Time"
msgstr ""
msgid "AlertManagement|Status"
msgstr ""
msgid "AlertManagement|Surface alerts in GitLab"
msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
msgid "AlertService|%{linkStart}Learn more%{linkEnd} about configuring this endpoint to receive alerts." msgid "AlertService|%{linkStart}Learn more%{linkEnd} about configuring this endpoint to receive alerts."
msgstr "" msgstr ""
...@@ -2730,9 +2769,6 @@ msgstr "" ...@@ -2730,9 +2769,6 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?" msgid "Authorize %{link_to_client} to use your account?"
msgstr "" msgstr ""
msgid "Authorize external service"
msgstr ""
msgid "Authorized %{new_chat_name}" msgid "Authorized %{new_chat_name}"
msgstr "" msgstr ""
...@@ -7343,9 +7379,6 @@ msgstr "" ...@@ -7343,9 +7379,6 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}" msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "" msgstr ""
msgid "Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents."
msgstr ""
msgid "Display name" msgid "Display name"
msgstr "" msgstr ""
...@@ -7835,9 +7868,6 @@ msgstr "" ...@@ -7835,9 +7868,6 @@ msgstr ""
msgid "Encountered an error while rendering: %{err}" msgid "Encountered an error while rendering: %{err}"
msgstr "" msgstr ""
msgid "End Time"
msgstr ""
msgid "End date" msgid "End date"
msgstr "" msgstr ""
...@@ -13664,12 +13694,6 @@ msgstr "" ...@@ -13664,12 +13694,6 @@ msgstr ""
msgid "No activities found" msgid "No activities found"
msgstr "" msgstr ""
msgid "No alerts available to display. If you think you're seeing this message in error, refresh the page."
msgstr ""
msgid "No alerts to display."
msgstr ""
msgid "No application_settings found" msgid "No application_settings found"
msgstr "" msgstr ""
...@@ -18777,9 +18801,6 @@ msgstr "" ...@@ -18777,9 +18801,6 @@ msgstr ""
msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings." msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings."
msgstr "" msgstr ""
msgid "Severity"
msgstr ""
msgid "Severity: %{severity}" msgid "Severity: %{severity}"
msgstr "" msgstr ""
...@@ -19565,9 +19586,6 @@ msgstr "" ...@@ -19565,9 +19586,6 @@ msgstr ""
msgid "Start GitLab Ultimate trial" msgid "Start GitLab Ultimate trial"
msgstr "" msgstr ""
msgid "Start Time"
msgstr ""
msgid "Start Web Terminal" msgid "Start Web Terminal"
msgstr "" msgstr ""
...@@ -20081,9 +20099,6 @@ msgstr "" ...@@ -20081,9 +20099,6 @@ msgstr ""
msgid "Support page URL" msgid "Support page URL"
msgstr "" msgstr ""
msgid "Surface alerts in GitLab"
msgstr ""
msgid "Switch branch/tag" msgid "Switch branch/tag"
msgstr "" msgstr ""
......
import { createLocalVue, mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlEmptyState, GlTable, GlAlert } from '@gitlab/ui'; import { GlEmptyState, GlTable, GlAlert, GlLoadingIcon } from '@gitlab/ui';
import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children'; import stubChildren from 'helpers/stub_children';
import AlertManagementList from '~/alert_management/components/alert_management_list.vue'; import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('AlertManagementList', () => { describe('AlertManagementList', () => {
let wrapper; let wrapper;
let store;
const findAlertsTable = () => wrapper.find(GlTable); const findAlertsTable = () => wrapper.find(GlTable);
const findAlert = () => wrapper.find(GlAlert); const findAlert = () => wrapper.find(GlAlert);
const findLoader = () => wrapper.find(GlLoadingIcon);
function mountComponent({ stubs = {}, alertManagementEnabled = false } = {}) { function mountComponent({
stubs = {},
props = { alertManagementEnabled: false },
data = {},
loading = false,
} = {}) {
wrapper = mount(AlertManagementList, { wrapper = mount(AlertManagementList, {
localVue,
store,
propsData: { propsData: {
indexPath: '/path', indexPath: '/path',
enableAlertManagementPath: '/link', enableAlertManagementPath: '/link',
emptyAlertSvgPath: 'illustration/path', emptyAlertSvgPath: 'illustration/path',
alertManagementEnabled, ...props,
},
data() {
return data;
},
mocks: {
$apollo: {
queries: {
alerts: {
loading,
},
},
},
}, },
stubs: { stubs: {
...stubChildren(AlertManagementList), ...stubChildren(AlertManagementList),
...@@ -32,20 +43,12 @@ describe('AlertManagementList', () => { ...@@ -32,20 +43,12 @@ describe('AlertManagementList', () => {
} }
beforeEach(() => { beforeEach(() => {
store = new Vuex.Store({
modules: {
list: {
namespaced: true,
},
},
});
mountComponent(); mountComponent();
}); });
afterEach(() => { afterEach(() => {
if (wrapper) { if (wrapper) {
wrapper.destroy(); wrapper.destroy();
store = null;
} }
}); });
...@@ -56,18 +59,41 @@ describe('AlertManagementList', () => { ...@@ -56,18 +59,41 @@ describe('AlertManagementList', () => {
}); });
describe('Alerts table', () => { describe('Alerts table', () => {
it('shows empty list', () => { it('loading state', () => {
store.state.list = { mountComponent({
alerts: [], stubs: { GlTable },
loading: false, props: { alertManagementEnabled: true },
}; data: { alerts: null },
loading: true,
});
expect(findAlertsTable().exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
mountComponent({ alertManagementEnabled: true }); it('error state', () => {
mountComponent({
stubs: { GlTable },
props: { alertManagementEnabled: true },
data: { alerts: null, errored: true },
loading: false,
});
expect(findAlertsTable().exists()).toBe(true);
expect(findAlertsTable().text()).toContain('No alerts to display');
expect(findLoader().exists()).toBe(false);
expect(findAlert().props().variant).toBe('danger');
});
return wrapper.vm.$nextTick().then(() => { it('empty state', () => {
expect(findAlertsTable().exists()).toBe(true); mountComponent({
expect(findAlert().text()).toContain('No alerts available to display'); stubs: { GlTable },
props: { alertManagementEnabled: true },
data: { alerts: [], errored: false },
loading: false,
}); });
expect(findAlertsTable().exists()).toBe(true);
expect(findAlertsTable().text()).toContain('No alerts to display');
expect(findLoader().exists()).toBe(false);
expect(findAlert().props().variant).toBe('info');
}); });
}); });
}); });
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