Commit f5a4733c authored by Tristan Read's avatar Tristan Read Committed by Kushal Pandya

Add runbook to metric dropdown

parent 7fa48bd7
......@@ -311,6 +311,7 @@ export default {
:disabled="formDisabled"
data-testid="alertRunbookField"
type="text"
:placeholder="s__('PrometheusAlerts|https://gitlab.com/gitlab-com/runbooks')"
/>
</gl-form-group>
</div>
......
<script>
import { mapState } from 'vuex';
import { pickBy } from 'lodash';
import { mapValues, pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility';
import {
GlResizeObserverDirective,
GlIcon,
GlLink,
GlLoadingIcon,
GlNewDropdown as GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlNewDropdownDivider as GlDropdownDivider,
GlModal,
GlModalDirective,
GlSprintf,
GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui';
......@@ -44,12 +46,14 @@ export default {
MonitorEmptyChart,
AlertWidget,
GlIcon,
GlLink,
GlLoadingIcon,
GlTooltip,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlModal,
GlSprintf,
},
directives: {
GlResizeObserver: GlResizeObserverDirective,
......@@ -341,6 +345,19 @@ export default {
this.$refs.copyChartLink.$el.firstChild.click();
}
},
getAlertRunbooks(queries) {
const hasRunbook = alert => Boolean(alert.runbookUrl);
const graphAlertsWithRunbooks = pickBy(this.getGraphAlerts(queries), hasRunbook);
const alertToRunbookTransform = alert => {
const alertQuery = queries.find(query => query.metricId === alert.metricId);
return {
key: alert.metricId,
href: alert.runbookUrl,
label: alertQuery.label,
};
};
return mapValues(graphAlertsWithRunbooks, alertToRunbookTransform);
},
},
panelTypes,
};
......@@ -436,6 +453,25 @@ export default {
>
{{ __('Alerts') }}
</gl-dropdown-item>
<gl-dropdown-item
v-for="runbook in getAlertRunbooks(graphData.metrics)"
:key="runbook.key"
:href="safeUrl(runbook.href)"
data-testid="runbookLink"
target="_blank"
rel="noopener noreferrer"
>
<span class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
<span>
<gl-sprintf :message="s__('Metrics|View runbook - %{label}')">
<template #label>
{{ runbook.label }}
</template>
</gl-sprintf>
</span>
<gl-icon name="external-link" />
</span>
</gl-dropdown-item>
<template v-if="graphData.links && graphData.links.length">
<gl-dropdown-divider />
......
---
title: Add runbook to metric chart dropdown
merge_request: 39288
author:
type: added
......@@ -118,7 +118,7 @@
.alert-modal-message {
margin-left: -1rem;
margin-right: -3rem;
margin-right: -1rem;
margin-top: -1rem;
}
......
......@@ -15433,6 +15433,9 @@ msgstr ""
msgid "Metrics|View logs"
msgstr ""
msgid "Metrics|View runbook - %{label}"
msgstr ""
msgid "Metrics|Y-axis label"
msgstr ""
......@@ -19429,6 +19432,9 @@ msgstr ""
msgid "PrometheusAlerts|Threshold"
msgstr ""
msgid "PrometheusAlerts|https://gitlab.com/gitlab-com/runbooks"
msgstr ""
msgid "PrometheusService|%{exporters} with %{metrics} were found"
msgstr ""
......
......@@ -9,6 +9,7 @@ import AlertWidget from '~/monitoring/components/alert_widget.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import {
mockAlert,
mockLogsHref,
mockLogsPath,
mockNamespace,
......@@ -55,9 +56,10 @@ describe('Dashboard Panel', () => {
const findCtxMenu = () => wrapper.find({ ref: 'contextualMenu' });
const findMenuItems = () => wrapper.findAll(GlDropdownItem);
const findMenuItemByText = text => findMenuItems().filter(i => i.text() === text);
const findAlertsWidget = () => wrapper.find(AlertWidget);
const createWrapper = (props, options) => {
wrapper = shallowMount(DashboardPanel, {
const createWrapper = (props, { mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(DashboardPanel, {
propsData: {
graphData,
settingsPath: dashboardProps.settingsPath,
......@@ -78,6 +80,9 @@ describe('Dashboard Panel', () => {
});
};
const setMetricsSavedToDb = val =>
monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val);
beforeEach(() => {
setTestTimeout(1000);
......@@ -601,10 +606,6 @@ describe('Dashboard Panel', () => {
});
describe('panel alerts', () => {
const setMetricsSavedToDb = val =>
monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val);
const findAlertsWidget = () => wrapper.find(AlertWidget);
beforeEach(() => {
mockGetterReturnValue('metricsSavedToDb', []);
......@@ -730,4 +731,60 @@ describe('Dashboard Panel', () => {
expect(findManageLinksItem().exists()).toBe(false);
});
});
describe('Runbook url', () => {
const findRunbookLinks = () => wrapper.findAll('[data-testid="runbookLink"]');
const { metricId } = graphData.metrics[0];
const { alert_path: alertPath } = mockAlert;
const mockRunbookAlert = {
...mockAlert,
metricId,
};
beforeEach(() => {
mockGetterReturnValue('metricsSavedToDb', []);
});
it('does not show a runbook link when alerts are not present', () => {
createWrapper();
expect(findRunbookLinks().length).toBe(0);
});
describe('when alerts are present', () => {
beforeEach(() => {
setMetricsSavedToDb([metricId]);
createWrapper({
alertsEndpoint: '/endpoint',
prometheusAlertsAvailable: true,
});
});
it('does not show a runbook link when a runbook is not set', async () => {
findAlertsWidget().vm.$emit('setAlerts', alertPath, {
...mockRunbookAlert,
runbookUrl: '',
});
await wrapper.vm.$nextTick();
expect(findRunbookLinks().length).toBe(0);
});
it('shows a runbook link when a runbook is set', async () => {
findAlertsWidget().vm.$emit('setAlerts', alertPath, mockRunbookAlert);
await wrapper.vm.$nextTick();
expect(findRunbookLinks().length).toBe(1);
expect(
findRunbookLinks()
.at(0)
.attributes('href'),
).toBe(invalidUrl);
});
});
});
});
import invalidUrl from '~/lib/utils/invalid_url';
// This import path needs to be relative for now because this mock data is used in
// Karma specs too, where the helpers/test_constants alias can not be resolved
import { TEST_HOST } from '../helpers/test_constants';
......@@ -630,3 +631,14 @@ export const dashboardActionsMenuProps = {
validateQueryPath: 'https://path/to/validateQuery',
isOotbDashboard: true,
};
export const mockAlert = {
alert_path: 'alert_path',
id: 8,
metricId: 'mock_metric_id',
operator: '>',
query: 'testQuery',
runbookUrl: invalidUrl,
threshold: 5,
title: 'alert title',
};
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