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';
import { toggleContainerClasses } from '~/lib/utils/dom_utils';
import SystemNote from './system_notes/system_note.vue';
import AlertSidebar from './alert_sidebar.vue';
import AlertMetrics from './alert_metrics.vue';
const containerEl = document.querySelector('.page-with-contextual-sidebar');
......@@ -36,6 +37,7 @@ export default {
),
fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
overviewTitle: s__('AlertManagement|Overview'),
metricsTitle: s__('AlertManagement|Metrics'),
reportedAt: s__('AlertManagement|Reported %{when}'),
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
},
......@@ -53,6 +55,7 @@ export default {
TimeAgoTooltip,
AlertSidebar,
SystemNote,
AlertMetrics,
},
inject: {
projectPath: {
......@@ -329,6 +332,9 @@ export default {
</template>
</gl-table>
</gl-tab>
<gl-tab data-testId="metricsTab" :title="$options.i18n.metricsTitle">
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
</gl-tab>
</gl-tabs>
<alert-sidebar
: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 {
...AlertListItem
createdAt
monitoringTool
metricsDashboardUrl
service
description
updatedAt
......@@ -15,4 +16,5 @@ fragment AlertDetailItem on AlertManagementAlert {
...AlertNote
}
}
}
......@@ -374,8 +374,8 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch }) => {
},
})
.then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard)
.then(({ schemaValidationWarnings }) => {
const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0;
.then(({ schemaValidationWarnings } = {}) => {
const hasWarnings = schemaValidationWarnings?.length !== 0;
/**
* 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
......
---
title: Surface metrics charts on the alert detail page
merge_request: 35044
author:
type: added
......@@ -1993,6 +1993,12 @@ msgstr ""
msgid "AlertManagement|Medium"
msgstr ""
msgid "AlertManagement|Metrics"
msgstr ""
msgid "AlertManagement|Metrics weren't available in the alerts payload."
msgstr ""
msgid "AlertManagement|More information"
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