Commit 56766e21 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Illya Klymov

Alert metrics chart

parent 8a8bf427
...@@ -26,6 +26,7 @@ import Tracking from '~/tracking'; ...@@ -26,6 +26,7 @@ import Tracking from '~/tracking';
import { toggleContainerClasses } from '~/lib/utils/dom_utils'; import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import SystemNote from './system_notes/system_note.vue'; import SystemNote from './system_notes/system_note.vue';
import AlertSidebar from './alert_sidebar.vue'; import AlertSidebar from './alert_sidebar.vue';
import AlertMetrics from './alert_metrics.vue';
const containerEl = document.querySelector('.page-with-contextual-sidebar'); const containerEl = document.querySelector('.page-with-contextual-sidebar');
...@@ -36,6 +37,7 @@ export default { ...@@ -36,6 +37,7 @@ export default {
), ),
fullAlertDetailsTitle: s__('AlertManagement|Alert details'), fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
overviewTitle: s__('AlertManagement|Overview'), overviewTitle: s__('AlertManagement|Overview'),
metricsTitle: s__('AlertManagement|Metrics'),
reportedAt: s__('AlertManagement|Reported %{when}'), reportedAt: s__('AlertManagement|Reported %{when}'),
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'), reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
}, },
...@@ -53,6 +55,7 @@ export default { ...@@ -53,6 +55,7 @@ export default {
TimeAgoTooltip, TimeAgoTooltip,
AlertSidebar, AlertSidebar,
SystemNote, SystemNote,
AlertMetrics,
}, },
inject: { inject: {
projectPath: { projectPath: {
...@@ -329,6 +332,9 @@ export default { ...@@ -329,6 +332,9 @@ export default {
</template> </template>
</gl-table> </gl-table>
</gl-tab> </gl-tab>
<gl-tab data-testId="metricsTab" :title="$options.i18n.metricsTitle">
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
</gl-tab>
</gl-tabs> </gl-tabs>
<alert-sidebar <alert-sidebar
:alert="alert" :alert="alert"
......
<script>
import Vue from 'vue';
import Vuex from 'vuex';
import * as Sentry from '@sentry/browser';
Vue.use(Vuex);
export default {
props: {
dashboardUrl: {
type: String,
required: false,
default: '',
},
},
data() {
return {
metricEmbedComponent: null,
namespace: 'alertMetrics',
};
},
mounted() {
if (this.dashboardUrl) {
Promise.all([
import('~/monitoring/components/embeds/metric_embed.vue'),
import('~/monitoring/stores'),
])
.then(([{ default: MetricEmbed }, { monitoringDashboard }]) => {
this.$store = new Vuex.Store({
modules: {
[this.namespace]: monitoringDashboard,
},
});
this.metricEmbedComponent = MetricEmbed;
})
.catch(e => Sentry.captureException(e));
}
},
};
</script>
<template>
<div class="gl-py-3">
<div v-if="dashboardUrl" ref="metricsChart">
<component
:is="metricEmbedComponent"
v-if="metricEmbedComponent"
:dashboard-url="dashboardUrl"
:namespace="namespace"
/>
</div>
<div v-else ref="emptyState">
{{ s__("AlertManagement|Metrics weren't available in the alerts payload.") }}
</div>
</div>
</template>
...@@ -5,6 +5,7 @@ fragment AlertDetailItem on AlertManagementAlert { ...@@ -5,6 +5,7 @@ fragment AlertDetailItem on AlertManagementAlert {
...AlertListItem ...AlertListItem
createdAt createdAt
monitoringTool monitoringTool
metricsDashboardUrl
service service
description description
updatedAt updatedAt
...@@ -15,4 +16,5 @@ fragment AlertDetailItem on AlertManagementAlert { ...@@ -15,4 +16,5 @@ fragment AlertDetailItem on AlertManagementAlert {
...AlertNote ...AlertNote
} }
} }
} }
...@@ -374,8 +374,8 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch }) => { ...@@ -374,8 +374,8 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch }) => {
}, },
}) })
.then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard) .then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
.then(({ schemaValidationWarnings }) => { .then(({ schemaValidationWarnings } = {}) => {
const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0; const hasWarnings = schemaValidationWarnings?.length !== 0;
/** /**
* The payload of the dispatch is a boolean, because at the moment a standard * The payload of the dispatch is a boolean, because at the moment a standard
* warning message is shown instead of the warnings the BE returns * warning message is shown instead of the warnings the BE returns
......
---
title: Surface metrics charts on the alert detail page
merge_request: 35044
author:
type: added
...@@ -1993,6 +1993,12 @@ msgstr "" ...@@ -1993,6 +1993,12 @@ msgstr ""
msgid "AlertManagement|Medium" msgid "AlertManagement|Medium"
msgstr "" msgstr ""
msgid "AlertManagement|Metrics"
msgstr ""
msgid "AlertManagement|Metrics weren't available in the alerts payload."
msgstr ""
msgid "AlertManagement|More information" msgid "AlertManagement|More information"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import AlertMetrics from '~/alert_management/components/alert_metrics.vue';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
jest.mock('~/monitoring/stores', () => ({
monitoringDashboard: {},
}));
const mockEmbedName = 'MetricsEmbedStub';
jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({
name: mockEmbedName,
render(h) {
return h('div');
},
}));
describe('Alert Metrics', () => {
let wrapper;
const mock = new MockAdapter(axios);
function mountComponent({ props } = {}) {
wrapper = shallowMount(AlertMetrics, {
propsData: {
...props,
},
stubs: {
MetricEmbed: true,
},
});
}
const findChart = () => wrapper.find({ name: mockEmbedName });
const findEmptyState = () => wrapper.find({ ref: 'emptyState' });
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
afterAll(() => {
mock.restore();
});
describe('Empty state', () => {
it('should display a message when metrics dashboard url is not provided ', () => {
mountComponent();
expect(findChart().exists()).toBe(false);
expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload.");
});
});
describe('Chart', () => {
it('should be rendered when dashboard url is provided', async () => {
mountComponent({ props: { dashboardUrl: 'metrics.url' } });
await waitForPromises();
await wrapper.vm.$nextTick();
expect(findEmptyState().exists()).toBe(false);
expect(findChart().exists()).toBe(true);
});
});
});
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