Commit 4183ce5c authored by Mike Greiling's avatar Mike Greiling

Merge branch '6036-graphalerts' into 'master'

Resolve "Show alert thresholds on dashboard"

Closes #6036

See merge request gitlab-org/gitlab-ee!7538
parents 592ff4e9 d774d714
...@@ -252,12 +252,26 @@ export default { ...@@ -252,12 +252,26 @@ export default {
:small-graph="forceSmallGraph" :small-graph="forceSmallGraph"
> >
<!-- EE content --> <!-- EE content -->
<template
slot="additionalSvgContent"
scope="{ graphDrawData }"
>
<threshold-lines
v-for="(alert, alertName) in alertData[graphData.id]"
:key="alertName"
:operator="alert.operator"
:threshold="alert.threshold"
:graph-draw-data="graphDrawData"
/>
</template>
<alert-widget <alert-widget
v-if="alertsEndpoint && graphData.id" v-if="alertsEndpoint && graphData.id"
:alerts-endpoint="alertsEndpoint" :alerts-endpoint="alertsEndpoint"
:label="getGraphLabel(graphData)" :label="getGraphLabel(graphData)"
:current-alerts="getQueryAlerts(graphData)" :current-alerts="getQueryAlerts(graphData)"
:custom-metric-id="graphData.id" :custom-metric-id="graphData.id"
:alert-data="alertData[graphData.id]"
@setAlerts="setAlerts"
/> />
</graph> </graph>
</graph-group> </graph-group>
......
...@@ -82,6 +82,7 @@ export default { ...@@ -82,6 +82,7 @@ export default {
showFlag: false, showFlag: false,
showFlagContent: false, showFlagContent: false,
timeSeries: [], timeSeries: [],
graphDrawData: {},
realPixelRatio: 1, realPixelRatio: 1,
seriesUnderMouse: [], seriesUnderMouse: [],
}; };
...@@ -180,12 +181,12 @@ export default { ...@@ -180,12 +181,12 @@ export default {
}); });
}, },
renderAxesPaths() { renderAxesPaths() {
this.timeSeries = createTimeSeries( ({ timeSeries: this.timeSeries, graphDrawData: this.graphDrawData } = createTimeSeries(
this.graphData.queries, this.graphData.queries,
this.graphWidth, this.graphWidth,
this.graphHeight, this.graphHeight,
this.graphHeightOffset, this.graphHeightOffset,
); ));
if (_.findWhere(this.timeSeries, { renderCanary: true })) { if (_.findWhere(this.timeSeries, { renderCanary: true })) {
this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true })); this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true }));
...@@ -288,6 +289,10 @@ export default { ...@@ -288,6 +289,10 @@ export default {
:viewBox="innerViewBox" :viewBox="innerViewBox"
class="graph-data" class="graph-data"
> >
<slot
name="additionalSvgContent"
:graphDrawData="graphDrawData"
/>
<graph-path <graph-path
v-for="(path, index) in timeSeries" v-for="(path, index) in timeSeries"
:key="index" :key="index"
......
...@@ -30,7 +30,7 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple']; ...@@ -30,7 +30,7 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
const defaultStyleOrder = ['solid', 'dashed', 'dotted']; const defaultStyleOrder = ['solid', 'dashed', 'dotted'];
function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) { function queryTimeSeries(query, graphDrawData, lineStyle) {
let usedColors = []; let usedColors = [];
let renderCanary = false; let renderCanary = false;
const timeSeriesParsed = []; const timeSeriesParsed = [];
...@@ -64,7 +64,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -64,7 +64,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
// but we need a regularly-spaced set of time/value pairs // but we need a regularly-spaced set of time/value pairs
// this gives us a complete range of one minute intervals // this gives us a complete range of one minute intervals
// offset the same amount as the original data // offset the same amount as the original data
const [minX, maxX] = xDom; const [minX, maxX] = graphDrawData.xDom;
const offset = d3.timeMinute(minX) - Number(minX); const offset = d3.timeMinute(minX) - Number(minX);
const datesWithoutGaps = d3.timeSecond.every(60) const datesWithoutGaps = d3.timeSecond.every(60)
.range(d3.timeMinute.offset(minX, -1), maxX) .range(d3.timeMinute.offset(minX, -1), maxX)
...@@ -84,31 +84,6 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -84,31 +84,6 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
renderCanary = true; renderCanary = true;
} }
const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]);
const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(xDom);
timeSeriesScaleX.ticks(d3.timeMinute, 60);
timeSeriesScaleY.domain(yDom);
const defined = d => !Number.isNaN(d.value) && d.value != null;
const lineFunction = d3
.line()
.defined(defined)
.curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
const areaFunction = d3
.area()
.defined(defined)
.curve(d3.curveLinear)
.x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset)
.y1(d => timeSeriesScaleY(d.value));
const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]];
const seriesCustomizationData = const seriesCustomizationData =
query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel });
...@@ -144,10 +119,10 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -144,10 +119,10 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
})); }));
timeSeriesParsed.push({ timeSeriesParsed.push({
linePath: lineFunction(values), linePath: graphDrawData.lineFunction(values),
areaPath: areaFunction(values), areaPath: graphDrawData.areaBelowLine(values),
timeSeriesScaleX, timeSeriesScaleX: graphDrawData.timeSeriesScaleX,
timeSeriesScaleY, timeSeriesScaleY: graphDrawData.timeSeriesScaleY,
values: timeSeries.values, values: timeSeries.values,
max: maximumValue, max: maximumValue,
average: accum / timeSeries.values.length, average: accum / timeSeries.values.length,
...@@ -164,7 +139,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom ...@@ -164,7 +139,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
return timeSeriesParsed; return timeSeriesParsed;
} }
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { function xyDomain(queries) {
const allValues = queries.reduce( const allValues = queries.reduce(
(allQueryResults, query) => (allQueryResults, query) =>
allQueryResults.concat( allQueryResults.concat(
...@@ -176,10 +151,70 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph ...@@ -176,10 +151,70 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph
const xDom = d3.extent(allValues, d => d.time); const xDom = d3.extent(allValues, d => d.time);
const yDom = [0, d3.max(allValues.map(d => d.value))]; const yDom = [0, d3.max(allValues.map(d => d.value))];
return queries.reduce((series, query, index) => { return {
xDom,
yDom,
};
}
export function generateGraphDrawData(queries, graphWidth, graphHeight, graphHeightOffset) {
const { xDom, yDom } = xyDomain(queries);
const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]);
const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]);
timeSeriesScaleX.domain(xDom);
timeSeriesScaleX.ticks(d3.timeMinute, 60);
timeSeriesScaleY.domain(yDom);
const defined = d => !Number.isNaN(d.value) && d.value != null;
const lineFunction = d3
.line()
.defined(defined)
.curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate
.x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value));
const areaBelowLine = d3
.area()
.defined(defined)
.curve(d3.curveLinear)
.x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset)
.y1(d => timeSeriesScaleY(d.value));
const areaAboveLine = d3
.area()
.defined(defined)
.curve(d3.curveLinear)
.x(d => timeSeriesScaleX(d.time))
.y0(0)
.y1(d => timeSeriesScaleY(d.value));
return {
lineFunction,
areaBelowLine,
areaAboveLine,
xDom,
yDom,
timeSeriesScaleX,
timeSeriesScaleY,
};
}
export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) {
const graphDrawData = generateGraphDrawData(queries, graphWidth, graphHeight, graphHeightOffset);
const timeSeries = queries.reduce((series, query, index) => {
const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length];
return series.concat( return series.concat(
queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), queryTimeSeries(query, graphDrawData, lineStyle),
); );
}, []); }, []);
return {
timeSeries,
graphDrawData,
};
} }
...@@ -28,6 +28,11 @@ export default { ...@@ -28,6 +28,11 @@ export default {
require: false, require: false,
default: null, default: null,
}, },
alertData: {
type: Object,
required: false,
default: () => ({}),
},
}, },
data() { data() {
return { return {
...@@ -36,7 +41,6 @@ export default { ...@@ -36,7 +41,6 @@ export default {
isLoading: false, isLoading: false,
isOpen: false, isOpen: false,
alerts: this.currentAlerts, alerts: this.currentAlerts,
alertData: {},
}; };
}, },
computed: { computed: {
...@@ -59,7 +63,7 @@ export default { ...@@ -59,7 +63,7 @@ export default {
: s__('PrometheusAlerts|Add alert'); : s__('PrometheusAlerts|Add alert');
}, },
hasAlerts() { hasAlerts() {
return this.alerts.length > 0; return Object.keys(this.alertData).length > 0;
}, },
firstAlert() { firstAlert() {
return this.hasAlerts ? this.alerts[0] : undefined; return this.hasAlerts ? this.alerts[0] : undefined;
...@@ -95,7 +99,12 @@ export default { ...@@ -95,7 +99,12 @@ export default {
this.alerts.map(alertPath => this.alerts.map(alertPath =>
this.service this.service
.readAlert(alertPath) .readAlert(alertPath)
.then(alertData => this.$set(this.alertData, alertPath, alertData)), .then(alertData => {
this.$emit('setAlerts', this.customMetricId, {
...this.alertData,
[alertPath]: alertData,
});
}),
), ),
) )
.then(() => { .then(() => {
...@@ -125,7 +134,10 @@ export default { ...@@ -125,7 +134,10 @@ export default {
.then(response => { .then(response => {
const alertPath = response.alert_path; const alertPath = response.alert_path;
this.alerts.unshift(alertPath); this.alerts.unshift(alertPath);
this.$set(this.alertData, alertPath, newAlert); this.$emit('setAlerts', this.customMetricId, {
...this.alertData,
[alertPath]: newAlert,
});
this.isLoading = false; this.isLoading = false;
this.handleDropdownClose(); this.handleDropdownClose();
}) })
...@@ -140,7 +152,10 @@ export default { ...@@ -140,7 +152,10 @@ export default {
this.service this.service
.updateAlert(alert, updatedAlert) .updateAlert(alert, updatedAlert)
.then(() => { .then(() => {
this.$set(this.alertData, alert, updatedAlert); this.$emit('setAlerts', this.customMetricId, {
...this.alertData,
[alert]: updatedAlert,
});
this.isLoading = false; this.isLoading = false;
this.handleDropdownClose(); this.handleDropdownClose();
}) })
...@@ -154,7 +169,8 @@ export default { ...@@ -154,7 +169,8 @@ export default {
this.service this.service
.deleteAlert(alert) .deleteAlert(alert)
.then(() => { .then(() => {
this.$delete(this.alertData, alert); const { [alert]: _, ...otherItems } = this.alertData;
this.$emit('setAlerts', this.customMetricId, otherItems);
this.alerts = this.alerts.filter(alertPath => alert !== alertPath); this.alerts = this.alerts.filter(alertPath => alert !== alertPath);
this.isLoading = false; this.isLoading = false;
this.handleDropdownClose(); this.handleDropdownClose();
......
import AlertWidget from './alert_widget.vue'; import AlertWidget from './alert_widget.vue';
import ThresholdLines from './threshold_lines.vue';
export default { export default {
components: { components: {
AlertWidget, AlertWidget,
ThresholdLines,
}, },
props: { props: {
alertsEndpoint: { alertsEndpoint: {
...@@ -11,6 +13,11 @@ export default { ...@@ -11,6 +13,11 @@ export default {
default: null, default: null,
}, },
}, },
data() {
return {
alertData: {},
};
},
methods: { methods: {
getGraphLabel(graphData) { getGraphLabel(graphData) {
if (!graphData.queries || !graphData.queries[0]) return undefined; if (!graphData.queries || !graphData.queries[0]) return undefined;
...@@ -20,5 +27,8 @@ export default { ...@@ -20,5 +27,8 @@ export default {
if (!graphData.queries) return []; if (!graphData.queries) return [];
return graphData.queries.map(query => query.alert_path).filter(Boolean); return graphData.queries.map(query => query.alert_path).filter(Boolean);
}, },
setAlerts(metricId, alertData) {
this.$set(this.alertData, metricId, alertData);
},
}, },
}; };
<script>
const red50 = '#fef6f5';
const red400 = '#e05842';
export default {
props: {
operator: {
type: String,
required: true,
validator: val => ['=', '<', '>'].includes(val),
},
threshold: {
type: Number,
required: true,
},
graphDrawData: {
type: Object,
required: true,
},
},
computed: {
thresholdData() {
if (!this.graphDrawData.xDom) {
return [];
}
const [xMin, xMax] = this.graphDrawData.xDom;
const [yMin, yMax] = this.graphDrawData.yDom;
const outOfRange = (this.operator === '>' && this.threshold > yMax) ||
(this.operator === '<' && this.threshold < yMin);
if (outOfRange) {
return [];
}
return [
{ time: xMin, value: this.threshold },
{ time: xMax, value: this.threshold },
];
},
linePath() {
if (!this.graphDrawData.lineFunction) {
return '';
}
return this.graphDrawData.lineFunction(this.thresholdData);
},
areaPath() {
if (this.operator === '>') {
if (!this.graphDrawData.areaAboveLine) {
return '';
}
return this.graphDrawData.areaAboveLine(this.thresholdData);
} else if (this.operator === '<') {
if (!this.graphDrawData.areaBelowLine) {
return '';
}
return this.graphDrawData.areaBelowLine(this.thresholdData);
}
return '';
},
},
created() {
this.red50 = red50;
this.red400 = red400;
},
};
</script>
<template>
<g
v-if="thresholdData.length"
transform="translate(-5, 20)"
class="js-threshold-lines"
>
<path
v-if="areaPath"
:d="areaPath"
:fill="red50"
/>
<path
:d="linePath"
fill="none"
:stroke="red400"
stroke-width="1"
stroke-dasharray="solid"
/>
</g>
</template>
---
title: Show Alert Thresholds on monitoring dashboards
merge_request: 7538
author:
type: added
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint } from 'spec/monitoring/mock_data';
import propsData from 'spec/monitoring/dashboard_spec';
describe('Dashboard', () => {
let Component;
let mock;
let vm;
beforeEach(() => {
setFixtures(`
<div class="prometheus-graphs"></div>
<div class="nav-sidebar"></div>
`);
mock = new MockAdapter(axios);
Component = Vue.extend(Dashboard);
});
afterEach(() => {
mock.restore();
});
describe('metrics without alerts', () => {
it('does not show threshold lines', (done) => {
vm = new Component({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
},
});
setTimeout(() => {
expect(vm.$el).not.toContainElement('.js-threshold-lines');
done();
});
});
});
describe('metrics with alert', () => {
const metricId = 5;
const alertParams = {
operator: '<',
threshold: 4,
prometheus_metric_id: metricId,
};
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
vm = new Component({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
},
});
});
it('shows single threshold line', (done) => {
vm.setAlerts(metricId, {
alertName: alertParams,
});
setTimeout(() => {
expect(vm.$el.querySelectorAll('.js-threshold-lines').length).toEqual(1);
done();
});
});
it('shows multiple threshold lines', (done) => {
vm.setAlerts(metricId, {
someAlert: alertParams,
otherAlert: alertParams,
});
setTimeout(() => {
expect(vm.$el.querySelectorAll('.js-threshold-lines').length).toEqual(2);
done();
});
});
});
});
import Vue from 'vue';
import ThresholdLines from 'ee/monitoring/components/threshold_lines.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { generateGraphDrawData } from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from 'spec/monitoring/mock_data';
const width = 500;
const height = 200;
const heightOffset = 50;
describe('ThresholdLines', () => {
let Component;
let vm;
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const { queries } = convertedMetrics[0];
const graphDrawData = generateGraphDrawData(queries, width, height, heightOffset);
beforeEach(() => {
Component = Vue.extend(ThresholdLines);
spyOn(graphDrawData, 'areaAboveLine').and.callThrough();
spyOn(graphDrawData, 'areaBelowLine').and.callThrough();
spyOn(graphDrawData, 'lineFunction').and.callThrough();
});
describe('< alerts', () => {
beforeEach(() => {
const props = {
operator: '<',
threshold: 0.6,
graphDrawData,
};
vm = mountComponent(Component, props);
});
it('fills area', () => {
expect(vm.$el.querySelectorAll('path').length).toEqual(2);
expect(graphDrawData.areaBelowLine).toHaveBeenCalled();
expect(graphDrawData.lineFunction).toHaveBeenCalled();
});
});
describe('> alerts', () => {
it('fills area', () => {
const props = {
operator: '>',
threshold: 0.6,
graphDrawData,
};
vm = mountComponent(Component, props);
expect(vm.$el.querySelectorAll('path').length).toEqual(2);
expect(graphDrawData.areaAboveLine).toHaveBeenCalled();
expect(graphDrawData.lineFunction).toHaveBeenCalled();
});
it('hides area if threshold out of range', () => {
const props = {
operator: '>',
threshold: 1000,
graphDrawData,
};
vm = mountComponent(Component, props);
expect(vm.$el.innerHTML).not.toBeDefined();
expect(graphDrawData.areaAboveLine).not.toHaveBeenCalled();
expect(graphDrawData.lineFunction).not.toHaveBeenCalled();
});
});
describe('= alerts', () => {
it('draws line only', () => {
const props = {
operator: '=',
threshold: 0.6,
graphDrawData,
};
vm = mountComponent(Component, props);
expect(vm.$el.querySelectorAll('path').length).toEqual(1);
expect(graphDrawData.lineFunction).toHaveBeenCalled();
});
});
});
...@@ -13,6 +13,11 @@ describe('AlertWidget', () => { ...@@ -13,6 +13,11 @@ describe('AlertWidget', () => {
currentAlerts: ['my/alert.json'], currentAlerts: ['my/alert.json'],
}; };
const mockSetAlerts = (_, data) => {
/* eslint-disable-next-line no-underscore-dangle */
Vue.set(vm._props, 'alertData', data);
};
beforeAll(() => { beforeAll(() => {
AlertWidgetComponent = Vue.extend(AlertWidget); AlertWidgetComponent = Vue.extend(AlertWidget);
}); });
...@@ -69,7 +74,11 @@ describe('AlertWidget', () => { ...@@ -69,7 +74,11 @@ describe('AlertWidget', () => {
spyOn(AlertsService.prototype, 'readAlert').and.returnValue( spyOn(AlertsService.prototype, 'readAlert').and.returnValue(
Promise.resolve({ operator: '>', threshold: 42 }), Promise.resolve({ operator: '>', threshold: 42 }),
); );
vm = mountComponent(AlertWidgetComponent, props, '#alert-widget'); const propsWithAlertData = {
...props,
alertData: { 'my/alert.json': { operator: '>', threshold: 42 } },
};
vm = mountComponent(AlertWidgetComponent, propsWithAlertData, '#alert-widget');
setTimeout(() => setTimeout(() =>
vm.$nextTick(() => { vm.$nextTick(() => {
...@@ -140,6 +149,8 @@ describe('AlertWidget', () => { ...@@ -140,6 +149,8 @@ describe('AlertWidget', () => {
); );
vm = mountComponent(AlertWidgetComponent, { ...props, currentAlerts: [] }); vm = mountComponent(AlertWidgetComponent, { ...props, currentAlerts: [] });
vm.$on('setAlerts', mockSetAlerts);
vm.$refs.widgetForm.$emit('create', alertParams); vm.$refs.widgetForm.$emit('create', alertParams);
expect(AlertsService.prototype.createAlert).toHaveBeenCalledWith(alertParams); expect(AlertsService.prototype.createAlert).toHaveBeenCalledWith(alertParams);
...@@ -161,6 +172,8 @@ describe('AlertWidget', () => { ...@@ -161,6 +172,8 @@ describe('AlertWidget', () => {
spyOn(AlertsService.prototype, 'updateAlert').and.returnValue(Promise.resolve()); spyOn(AlertsService.prototype, 'updateAlert').and.returnValue(Promise.resolve());
vm = mountComponent(AlertWidgetComponent, { ...props, currentAlerts: [alertPath] }); vm = mountComponent(AlertWidgetComponent, { ...props, currentAlerts: [alertPath] });
vm.$on('setAlerts', mockSetAlerts);
vm.$refs.widgetForm.$emit('update', { vm.$refs.widgetForm.$emit('update', {
...alertParams, ...alertParams,
alert: alertPath, alert: alertPath,
...@@ -191,6 +204,8 @@ describe('AlertWidget', () => { ...@@ -191,6 +204,8 @@ describe('AlertWidget', () => {
spyOn(AlertsService.prototype, 'deleteAlert').and.returnValue(Promise.resolve()); spyOn(AlertsService.prototype, 'deleteAlert').and.returnValue(Promise.resolve());
vm = mountComponent(AlertWidgetComponent, { ...props, currentAlerts: [alertPath] }); vm = mountComponent(AlertWidgetComponent, { ...props, currentAlerts: [alertPath] });
vm.$on('setAlerts', mockSetAlerts);
vm.$refs.widgetForm.$emit('delete', { alert: alertPath }); vm.$refs.widgetForm.$emit('delete', { alert: alertPath });
expect(AlertsService.prototype.deleteAlert).toHaveBeenCalledWith(alertPath); expect(AlertsService.prototype.deleteAlert).toHaveBeenCalledWith(alertPath);
......
...@@ -4,30 +4,32 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; ...@@ -4,30 +4,32 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
settingsPath: '/path/to/settings',
clustersPath: '/path/to/clusters',
tagsPath: '/path/to/tags',
projectPath: '/path/to/project',
metricsEndpoint: mockApiEndpoint,
deploymentEndpoint: null,
emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
emptyLoadingSvgPath: '/path/to/loading.svg',
emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
environmentsEndpoint: '/root/hello-prometheus/environments/35',
currentEnvironmentName: 'production',
};
export default propsData;
describe('Dashboard', () => { describe('Dashboard', () => {
let DashboardComponent; let DashboardComponent;
const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
settingsPath: '/path/to/settings',
clustersPath: '/path/to/clusters',
tagsPath: '/path/to/tags',
projectPath: '/path/to/project',
metricsEndpoint: mockApiEndpoint,
deploymentEndpoint: null,
emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
emptyLoadingSvgPath: '/path/to/loading.svg',
emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
environmentsEndpoint: '/root/hello-prometheus/environments/35',
currentEnvironmentName: 'production',
};
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<div class="prometheus-graphs"></div> <div class="prometheus-graphs"></div>
<div class="nav-sidebar"></div> <div class="nav-sidebar"></div>
`); `);
DashboardComponent = Vue.extend(Dashboard); DashboardComponent = Vue.extend(Dashboard);
}); });
......
...@@ -8,7 +8,7 @@ const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeri ...@@ -8,7 +8,7 @@ const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeri
const defaultValuesComponent = {}; const defaultValuesComponent = {};
const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
defaultValuesComponent.timeSeries = timeSeries; defaultValuesComponent.timeSeries = timeSeries;
......
...@@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; ...@@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
describe('TrackInfo component', () => { describe('TrackInfo component', () => {
let vm; let vm;
......
...@@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; ...@@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data';
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120);
describe('TrackLine component', () => { describe('TrackLine component', () => {
let vm; let vm;
......
...@@ -13,7 +13,7 @@ const createComponent = (propsData) => { ...@@ -13,7 +13,7 @@ const createComponent = (propsData) => {
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120);
const firstTimeSeries = timeSeries[0]; const firstTimeSeries = timeSeries[0];
describe('Monitoring Paths', () => { describe('Monitoring Paths', () => {
......
...@@ -8,6 +8,7 @@ export const metricsGroupsAPIResponse = { ...@@ -8,6 +8,7 @@ export const metricsGroupsAPIResponse = {
priority: 1, priority: 1,
metrics: [ metrics: [
{ {
id: 5,
title: 'Memory usage', title: 'Memory usage',
weight: 1, weight: 1,
queries: [ queries: [
......
...@@ -2,7 +2,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; ...@@ -2,7 +2,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series';
import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data'; import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data';
const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries);
const timeSeries = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120);
const firstTimeSeries = timeSeries[0]; const firstTimeSeries = timeSeries[0];
describe('Multiple time series', () => { describe('Multiple time series', () => {
......
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