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>
import { mapState } from 'vuex';
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';
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: [
{
key: 'severity',
label: __('Severity'),
label: s__('AlertManagement|Severity'),
tdClass,
},
{
key: 'start_time',
label: __('Start Time'),
label: s__('AlertManagement|Start Time'),
tdClass,
},
{
key: 'end_time',
label: __('End Time'),
label: s__('AlertManagement|End Time'),
tdClass,
},
{
key: 'alert',
label: __('Alert'),
label: s__('AlertManagement|Alert'),
thClass: 'w-30p',
tdClass,
},
{
key: 'events',
label: __('Events'),
label: s__('AlertManagement|Events'),
tdClass,
},
{
key: 'status',
label: __('Status'),
label: s__('AlertManagement|Status'),
tdClass,
},
],
......@@ -66,15 +74,36 @@ export default {
required: true,
},
},
apollo: {
alerts: {
query: getAlerts,
variables() {
return {
projectPath: this.indexPath,
};
},
error() {
this.errored = true;
},
},
},
data() {
return {
alerts: null,
errored: false,
isAlertDismissed: false,
isErrorAlertDismissed: false,
};
},
computed: {
...mapState('list', ['alerts', 'loading']),
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 {
<div>
<div v-if="alertManagementEnabled" class="alert-management-list">
<gl-alert v-if="showNoAlertsMsg" @dismiss="isAlertDismissed = true">
{{
__(
`No alerts available to display. If you think you're seeing this message in error, refresh the page.`,
)
}}
{{ $options.i18n.noAlertsMsg }}
</gl-alert>
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true">
{{ $options.i18n.errorMsg }}
</gl-alert>
<div v-if="loading" class="py-3">
<gl-loading-icon size="md" />
</div>
<gl-table
class="mt-3"
:items="alerts"
:fields="$options.fields"
:show-empty="true"
:busy="loading"
fixed
stacked="sm"
tbody-tr-class="table-row mb-4"
>
<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>
</gl-table>
</div>
<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>
<div class="d-block">
<span>{{
__(
'Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
s__(
'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>
<a href="/help/user/project/operations/alert_management.html">
{{ __('More information') }}
{{ s__('AlertManagement|More information') }}
</a>
</div>
<div class="d-block center pt-4">
<gl-button category="primary" variant="success" :href="enableAlertManagementPath">
{{ __('Authorize external service') }}
{{ s__('AlertManagement|Authorize external service') }}
</gl-button>
</div>
</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 store from './store';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import AlertManagementList from './components/alert_management_list.vue';
Vue.use(VueApollo);
export default () => {
const selector = '#js-alert_management';
const domEl = document.querySelector(selector);
const { indexPath, enableAlertManagementPath, emptyAlertSvgPath } = domEl.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({
el: selector,
apolloProvider,
components: {
AlertManagementList,
},
store,
render(createElement) {
return createElement('alert-management-list', {
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"
msgstr[0] ""
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."
msgstr ""
......@@ -2730,9 +2769,6 @@ msgstr ""
msgid "Authorize %{link_to_client} to use your account?"
msgstr ""
msgid "Authorize external service"
msgstr ""
msgid "Authorized %{new_chat_name}"
msgstr ""
......@@ -7343,9 +7379,6 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
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"
msgstr ""
......@@ -7835,9 +7868,6 @@ msgstr ""
msgid "Encountered an error while rendering: %{err}"
msgstr ""
msgid "End Time"
msgstr ""
msgid "End date"
msgstr ""
......@@ -13664,12 +13694,6 @@ msgstr ""
msgid "No activities found"
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"
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."
msgstr ""
msgid "Severity"
msgstr ""
msgid "Severity: %{severity}"
msgstr ""
......@@ -19565,9 +19586,6 @@ msgstr ""
msgid "Start GitLab Ultimate trial"
msgstr ""
msgid "Start Time"
msgstr ""
msgid "Start Web Terminal"
msgstr ""
......@@ -20081,9 +20099,6 @@ msgstr ""
msgid "Support page URL"
msgstr ""
msgid "Surface alerts in GitLab"
msgstr ""
msgid "Switch branch/tag"
msgstr ""
......
import { createLocalVue, mount } from '@vue/test-utils';
import { GlEmptyState, GlTable, GlAlert } from '@gitlab/ui';
import Vuex from 'vuex';
import { mount } from '@vue/test-utils';
import { GlEmptyState, GlTable, GlAlert, GlLoadingIcon } from '@gitlab/ui';
import stubChildren from 'helpers/stub_children';
import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('AlertManagementList', () => {
let wrapper;
let store;
const findAlertsTable = () => wrapper.find(GlTable);
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, {
localVue,
store,
propsData: {
indexPath: '/path',
enableAlertManagementPath: '/link',
emptyAlertSvgPath: 'illustration/path',
alertManagementEnabled,
...props,
},
data() {
return data;
},
mocks: {
$apollo: {
queries: {
alerts: {
loading,
},
},
},
},
stubs: {
...stubChildren(AlertManagementList),
......@@ -32,20 +43,12 @@ describe('AlertManagementList', () => {
}
beforeEach(() => {
store = new Vuex.Store({
modules: {
list: {
namespaced: true,
},
},
});
mountComponent();
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
store = null;
}
});
......@@ -56,18 +59,41 @@ describe('AlertManagementList', () => {
});
describe('Alerts table', () => {
it('shows empty list', () => {
store.state.list = {
alerts: [],
loading: false,
};
mountComponent({ alertManagementEnabled: true });
it('loading state', () => {
mountComponent({
stubs: { GlTable },
props: { alertManagementEnabled: true },
data: { alerts: null },
loading: true,
});
expect(findAlertsTable().exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
return wrapper.vm.$nextTick().then(() => {
it('error state', () => {
mountComponent({
stubs: { GlTable },
props: { alertManagementEnabled: true },
data: { alerts: null, errored: true },
loading: false,
});
expect(findAlertsTable().exists()).toBe(true);
expect(findAlert().text()).toContain('No alerts available to display');
expect(findAlertsTable().text()).toContain('No alerts to display');
expect(findLoader().exists()).toBe(false);
expect(findAlert().props().variant).toBe('danger');
});
it('empty state', () => {
mountComponent({
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