Commit 97172bbe authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '32778-clean-up-metrics-object-increase-timeout' into 'master'

Remove duplicate queries object and move it to dashboard panels object

See merge request gitlab-org/gitlab!20621
parents 2754fa28 c6f01133
......@@ -29,7 +29,7 @@ const AREA_COLOR_RGBA = `rgba(${hexToRgb(AREA_COLOR).join(',')},${AREA_OPACITY})
* time series chart, the boundary band shows the normal
* range of values the metric should take.
*
* This component accepts 3 queries, which contain the
* This component accepts 3 metrics, which contain the
* "metric", "upper" limit and "lower" limit.
*
* The upper and lower series are "stacked areas" visually
......@@ -62,10 +62,10 @@ export default {
},
computed: {
series() {
return this.graphData.queries.map(query => {
const values = query.result[0] ? query.result[0].values : [];
return this.graphData.metrics.map(metric => {
const values = metric.result && metric.result[0] ? metric.result[0].values : [];
return {
label: query.label,
label: metric.label,
// NaN values may disrupt avg., max. & min. calculations in the legend, filter them out
data: values.filter(([, value]) => !Number.isNaN(value)),
};
......@@ -83,7 +83,7 @@ export default {
return min < 0 ? -min : 0;
},
metricData() {
const originalMetricQuery = this.graphData.queries[0];
const originalMetricQuery = this.graphData.metrics[0];
const metricQuery = { ...originalMetricQuery };
metricQuery.result[0].values = metricQuery.result[0].values.map(([x, y]) => [
......@@ -93,7 +93,7 @@ export default {
return {
...this.graphData,
type: 'line-chart',
queries: [metricQuery],
metrics: [metricQuery],
};
},
metricSeriesConfig() {
......
......@@ -32,8 +32,8 @@ export default {
},
computed: {
chartData() {
const queryData = this.graphData.queries.reduce((acc, query) => {
const series = makeDataSeries(query.result, {
const queryData = this.graphData.metrics.reduce((acc, query) => {
const series = makeDataSeries(query.result || [], {
name: this.formatLegendLabel(query),
});
......@@ -45,13 +45,13 @@ export default {
};
},
xAxisTitle() {
return this.graphData.queries[0].result[0].x_label !== undefined
? this.graphData.queries[0].result[0].x_label
return this.graphData.metrics[0].result[0].x_label !== undefined
? this.graphData.metrics[0].result[0].x_label
: '';
},
yAxisTitle() {
return this.graphData.queries[0].result[0].y_label !== undefined
? this.graphData.queries[0].result[0].y_label
return this.graphData.metrics[0].result[0].y_label !== undefined
? this.graphData.metrics[0].result[0].y_label
: '';
},
xAxisType() {
......
......@@ -24,7 +24,7 @@ export default {
},
computed: {
chartData() {
return this.queries.result.reduce(
return this.metrics.result.reduce(
(acc, result, i) => [...acc, ...result.values.map((value, j) => [i, j, value[1]])],
[],
);
......@@ -36,7 +36,7 @@ export default {
return this.graphData.y_label || '';
},
xAxisLabels() {
return this.queries.result.map(res => Object.values(res.metric)[0]);
return this.metrics.result.map(res => Object.values(res.metric)[0]);
},
yAxisLabels() {
return this.result.values.map(val => {
......@@ -46,10 +46,10 @@ export default {
});
},
result() {
return this.queries.result[0];
return this.metrics.result[0];
},
queries() {
return this.graphData.queries[0];
metrics() {
return this.graphData.metrics[0];
},
},
};
......
......@@ -17,7 +17,7 @@ export default {
},
computed: {
queryInfo() {
return this.graphData.queries[0];
return this.graphData.metrics[0];
},
engineeringNotation() {
return `${roundOffFloat(this.queryInfo.result[0].value[1], 1)}${this.queryInfo.unit}`;
......
......@@ -105,7 +105,7 @@ export default {
// Transforms & supplements query data to render appropriate labels & styles
// Input: [{ queryAttributes1 }, { queryAttributes2 }]
// Output: [{ seriesAttributes1 }, { seriesAttributes2 }]
return this.graphData.queries.reduce((acc, query) => {
return this.graphData.metrics.reduce((acc, query) => {
const { appearance } = query;
const lineType =
appearance && appearance.line && appearance.line.type
......@@ -121,7 +121,7 @@ export default {
? appearance.area.opacity
: undefined,
};
const series = makeDataSeries(query.result, {
const series = makeDataSeries(query.result || [], {
name: this.formatLegendLabel(query),
lineStyle: {
type: lineType,
......
......@@ -250,14 +250,9 @@ export default {
'setEndpoints',
'setPanelGroupMetrics',
]),
chartsWithData(charts) {
return charts.filter(chart =>
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
updateMetrics(key, metrics) {
updateMetrics(key, panels) {
this.setPanelGroupMetrics({
metrics,
panels,
key,
});
},
......@@ -292,8 +287,13 @@ export default {
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
chartsWithData(panels) {
return panels.filter(panel =>
panel.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0;
return this.chartsWithData(group.panels).length > 0;
},
onDateTimePickerApply(timeWindowUrlParams) {
return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
......@@ -453,14 +453,14 @@ export default {
:collapse-group="groupHasData(groupData)"
>
<vue-draggable
:value="groupData.metrics"
:value="groupData.panels"
group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels"
@input="updateMetrics(groupData.key, $event)"
>
<div
v-for="(graphData, graphIndex) in groupData.metrics"
v-for="(graphData, graphIndex) in groupData.panels"
:key="`panel-type-${graphIndex}`"
class="col-12 col-lg-6 px-2 mb-2 draggable"
:class="{ 'draggable-enabled': isRearrangingPanels }"
......@@ -469,7 +469,7 @@ export default {
<div
v-if="isRearrangingPanels"
class="draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
@click="removeGraph(groupData.metrics, graphIndex)"
@click="removeGraph(groupData.panels, graphIndex)"
>
<a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')"
><icon name="close"
......
......@@ -37,11 +37,14 @@ export default {
computed: {
...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() {
if (!this.dashboard || !this.dashboard.panel_groups) {
return [];
}
const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)),
) || { metrics: [] };
group.panels.find(chart => this.chartHasData(chart)),
) || { panels: [] };
return groupWithMetrics.metrics.filter(chart => this.chartHasData(chart));
return groupWithMetrics.panels.filter(chart => this.chartHasData(chart));
},
isSingleChart() {
return this.charts.length === 1;
......
......@@ -54,10 +54,14 @@ export default {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
},
graphDataHasMetrics() {
return this.graphData.queries[0].result.length > 0;
return (
this.graphData.metrics &&
this.graphData.metrics[0].result &&
this.graphData.metrics[0].result.length > 0
);
},
csvText() {
const chartData = this.graphData.queries[0].result[0].values;
const chartData = this.graphData.metrics[0].result[0].values;
const yLabel = this.graphData.y_label;
const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
return chartData.reduce((csv, data) => {
......@@ -112,7 +116,7 @@ export default {
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.queries)"
:thresholds="getGraphAlertValues(graphData.metrics)"
group-id="panel-type-chart"
>
<div class="d-flex align-items-center">
......@@ -120,8 +124,8 @@ export default {
v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries"
:alerts-to-manage="getGraphAlerts(graphData.queries)"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
@setAlerts="setAlerts"
/>
<gl-dropdown
......
import Vue from 'vue';
import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types';
import { normalizeMetrics, normalizeMetric, normalizeQueryResult } from './utils';
import { normalizeMetric, normalizeQueryResult } from './utils';
const normalizePanel = panel => panel.metrics.map(normalizeMetric);
const normalizePanelMetrics = (metrics, defaultLabel) =>
metrics.map(metric => ({
...normalizeMetric(metric),
label: metric.label || defaultLabel,
}));
export default {
[types.REQUEST_METRICS_DATA](state) {
......@@ -13,28 +17,18 @@ export default {
[types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
state.dashboard.panel_groups = groupData.map((group, i) => {
const key = `${slugify(group.group || 'default')}-${i}`;
let { metrics = [], panels = [] } = group;
let { panels = [] } = group;
// each panel has metric information that needs to be normalized
panels = panels.map(panel => ({
...panel,
metrics: normalizePanel(panel),
}));
// for backwards compatibility, and to limit Vue template changes:
// for each group alias panels to metrics
// for each panel alias metrics to queries
metrics = panels.map(panel => ({
...panel,
queries: panel.metrics,
metrics: normalizePanelMetrics(panel.metrics, panel.y_label),
}));
return {
...group,
panels,
key,
metrics: normalizeMetrics(metrics),
};
});
......@@ -58,6 +52,7 @@ export default {
[types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) {
state.environments = [];
},
[types.SET_QUERY_RESULT](state, { metricId, result }) {
if (!metricId || !result || result.length === 0) {
return;
......@@ -65,14 +60,17 @@ export default {
state.showEmptyState = false;
/**
* Search the dashboard state for a matching id
*/
state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => {
metric.queries.forEach(query => {
if (query.metric_id === metricId) {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
if (metric.metric_id === metricId) {
state.metricsWithData.push(metricId);
// ensure dates/numbers are correctly formatted for charts
const normalizedResults = result.map(normalizeQueryResult);
Vue.set(query, 'result', Object.freeze(normalizedResults));
Vue.set(metric, 'result', Object.freeze(normalizedResults));
}
});
});
......@@ -101,6 +99,6 @@ export default {
},
[types.SET_PANEL_GROUP_METRICS](state, payload) {
const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key);
panelGroup.metrics = payload.metrics;
panelGroup.panels = payload.panels;
},
};
import _ from 'underscore';
function checkQueryEmptyData(query) {
return {
...query,
result: query.result.filter(timeSeries => {
const newTimeSeries = timeSeries;
const hasValue = series =>
!Number.isNaN(series[1]) && (series[1] !== null || series[1] !== undefined);
const hasNonNullValue = timeSeries.values.find(hasValue);
newTimeSeries.values = hasNonNullValue ? newTimeSeries.values : [];
return newTimeSeries.values.length > 0;
}),
};
}
function removeTimeSeriesNoData(queries) {
return queries.reduce((series, query) => series.concat(checkQueryEmptyData(query)), []);
}
// Metrics and queries are currently stored 1:1, so `queries` is an array of length one.
// We want to group queries onto a single chart by title & y-axis label.
// This function will no longer be required when metrics:queries are 1:many,
// though there is no consequence if the function stays in use.
// @param metrics [Array<Object>]
// Ex) [
// { id: 1, title: 'title', y_label: 'MB', queries: [{ ...query1Attrs }] },
// { id: 2, title: 'title', y_label: 'MB', queries: [{ ...query2Attrs }] },
// { id: 3, title: 'new title', y_label: 'MB', queries: [{ ...query3Attrs }] }
// ]
// @return [Array<Object>]
// Ex) [
// { title: 'title', y_label: 'MB', queries: [{ metricId: 1, ...query1Attrs },
// { metricId: 2, ...query2Attrs }] },
// { title: 'new title', y_label: 'MB', queries: [{ metricId: 3, ...query3Attrs }]}
// ]
export function groupQueriesByChartInfo(metrics) {
const metricsByChart = metrics.reduce((accumulator, metric) => {
const { queries, ...chart } = metric;
const chartKey = `${chart.title}|${chart.y_label}`;
accumulator[chartKey] = accumulator[chartKey] || { ...chart, queries: [] };
queries.forEach(queryAttrs => {
let metricId;
if (chart.id) {
metricId = chart.id.toString();
} else if (queryAttrs.metric_id) {
metricId = queryAttrs.metric_id.toString();
} else {
metricId = null;
}
accumulator[chartKey].queries.push({ metricId, ...queryAttrs });
});
return accumulator;
}, {});
return Object.values(metricsByChart);
}
export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`;
/**
* Not to confuse with normalizeMetrics (plural)
* Metrics loaded from project-defined dashboards do not have a metric_id.
* This method creates a unique ID combining metric_id and id, if either is present.
* This is hopefully a temporary solution until BE processes metrics before passing to fE
* @param {Object} metric - metric
* @returns {Object} - normalized metric with a uniqueID
*/
export const normalizeMetric = (metric = {}) =>
_.omit(
{
...metric,
metric_id: uniqMetricsId(metric),
metricId: uniqMetricsId(metric),
},
'id',
);
......@@ -93,6 +31,11 @@ export const normalizeQueryResult = timeSeries => {
Number(value),
]),
};
// Check result for empty data
normalizedResult.values = normalizedResult.values.filter(series => {
const hasValue = d => !Number.isNaN(d[1]) && (d[1] !== null || d[1] !== undefined);
return series.find(hasValue);
});
} else if (timeSeries.value) {
normalizedResult = {
...timeSeries,
......@@ -102,21 +45,3 @@ export const normalizeQueryResult = timeSeries => {
return normalizedResult;
};
export const normalizeMetrics = metrics => {
const groupedMetrics = groupQueriesByChartInfo(metrics);
return groupedMetrics.map(metric => {
const queries = metric.queries.map(query => ({
...query,
// custom metrics do not require a label, so we should ensure this attribute is defined
label: query.label || metric.y_label,
result: (query.result || []).map(normalizeQueryResult),
}));
return {
...metric,
queries: removeTimeSeriesNoData(queries),
};
});
};
......@@ -72,10 +72,9 @@ export const ISODateToString = date => dateformat(date, dateFormats.dateTimePick
*/
export const graphDataValidatorForValues = (isValues, graphData) => {
const responseValueKeyName = isValues ? 'value' : 'values';
return (
Array.isArray(graphData.queries) &&
graphData.queries.filter(query => {
Array.isArray(graphData.metrics) &&
graphData.metrics.filter(query => {
if (Array.isArray(query.result)) {
return (
query.result.filter(res => Array.isArray(res[responseValueKeyName])).length ===
......@@ -83,7 +82,7 @@ export const graphDataValidatorForValues = (isValues, graphData) => {
);
}
return false;
}).length === graphData.queries.length
}).length === graphData.metrics.filter(query => query.result).length
);
};
......@@ -131,7 +130,7 @@ export const downloadCSVOptions = title => {
};
/**
* This function validates the graph data contains exactly 3 queries plus
* This function validates the graph data contains exactly 3 metrics plus
* value validations from graphDataValidatorForValues.
* @param {Object} isValues
* @param {Object} graphData the graph data response from a prometheus request
......@@ -140,8 +139,8 @@ export const downloadCSVOptions = title => {
export const graphDataValidatorForAnomalyValues = graphData => {
const anomalySeriesCount = 3; // metric, upper, lower
return (
graphData.queries &&
graphData.queries.length === anomalySeriesCount &&
graphData.metrics &&
graphData.metrics.length === anomalySeriesCount &&
graphDataValidatorForValues(false, graphData)
);
};
......
......@@ -48,7 +48,7 @@ describe('Time series component', () => {
// Mock data contains 2 panels, pick the first one
store.commit(`monitoringDashboard/${types.SET_QUERY_RESULT}`, mockedQueryResultPayload);
[mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
[mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[0].panels;
makeTimeSeriesChart = (graphData, type) =>
shallowMount(TimeSeries, {
......@@ -235,7 +235,7 @@ describe('Time series component', () => {
});
it('utilizes all data points', () => {
const { values } = mockGraphData.queries[0].result[0];
const { values } = mockGraphData.metrics[0].result[0];
expect(chartData.length).toBe(1);
expect(seriesData().data.length).toBe(values.length);
......
......@@ -17,8 +17,8 @@ const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent
const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
const queries = anomalyMockResultValues[datasetName].map((values, index) => ({
...template.queries[index],
const metrics = anomalyMockResultValues[datasetName].map((values, index) => ({
...template.metrics[index],
result: [
{
metrics: {},
......@@ -26,7 +26,7 @@ const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
},
],
}));
return { ...template, queries };
return { ...template, metrics };
};
describe('Anomaly chart component', () => {
......@@ -67,19 +67,19 @@ describe('Anomaly chart component', () => {
describe('graph-data', () => {
it('receives a single "metric" series', () => {
const { graphData } = getTimeSeriesProps();
expect(graphData.queries.length).toBe(1);
expect(graphData.metrics.length).toBe(1);
});
it('receives "metric" with all data', () => {
const { graphData } = getTimeSeriesProps();
const query = graphData.queries[0];
const expectedQuery = makeAnomalyGraphData(dataSetName).queries[0];
const query = graphData.metrics[0];
const expectedQuery = makeAnomalyGraphData(dataSetName).metrics[0];
expect(query).toEqual(expectedQuery);
});
it('receives the "metric" results', () => {
const { graphData } = getTimeSeriesProps();
const { result } = graphData.queries[0];
const { result } = graphData.metrics[0];
const { values } = result[0];
const [metricDataset] = dataSet;
expect(values).toEqual(expect.any(Array));
......@@ -266,12 +266,12 @@ describe('Anomaly chart component', () => {
describe('graph-data', () => {
it('receives a single "metric" series', () => {
const { graphData } = getTimeSeriesProps();
expect(graphData.queries.length).toBe(1);
expect(graphData.metrics.length).toBe(1);
});
it('receives "metric" results and applies the offset to them', () => {
const { graphData } = getTimeSeriesProps();
const { result } = graphData.queries[0];
const { result } = graphData.metrics[0];
const { values } = result[0];
const [metricDataset] = dataSet;
expect(values).toEqual(expect.any(Array));
......
......@@ -62,7 +62,7 @@ describe('Embed', () => {
describe('metrics are available', () => {
beforeEach(() => {
store.state.monitoringDashboard.dashboard.panel_groups = groups;
store.state.monitoringDashboard.dashboard.panel_groups[0].metrics = metricsData;
store.state.monitoringDashboard.dashboard.panel_groups[0].panels = metricsData;
store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent();
......
......@@ -42,9 +42,7 @@ export const metrics = [
},
];
const queries = [
{
result: [
const result = [
{
values: [
['Mon', 1220],
......@@ -56,24 +54,22 @@ const queries = [
['Sun', 1320],
],
},
],
},
];
export const metricsData = [
{
queries,
metrics: [
{
metric_id: 15,
result,
},
],
},
{
queries,
metrics: [
{
metric_id: 16,
result,
},
],
},
......
......@@ -110,9 +110,6 @@ export const anomalyMockGraphData = {
type: 'anomaly-chart',
weight: 3,
metrics: [
// Not used
],
queries: [
{
metricId: '90',
id: 'metric',
......
......@@ -33,7 +33,7 @@ describe('Panel Type component', () => {
let glEmptyChart;
// Deep clone object before modifying
const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
graphDataNoResult.queries[0].result = [];
graphDataNoResult.metrics[0].result = [];
beforeEach(() => {
panelType = shallowMount(PanelType, {
......@@ -143,7 +143,7 @@ describe('Panel Type component', () => {
describe('csvText', () => {
it('converts metrics data from json to csv', () => {
const header = `timestamp,${graphDataPrometheusQueryRange.y_label}`;
const data = graphDataPrometheusQueryRange.queries[0].result[0].values;
const data = graphDataPrometheusQueryRange.metrics[0].result[0].values;
const firstRow = `${data[0][0]},${data[0][1]}`;
const secondRow = `${data[1][0]},${data[1][1]}`;
......
......@@ -27,7 +27,7 @@ describe('Monitoring mutations', () => {
it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedLabel = 'Pod average';
const { label, query_range } = stateCopy.dashboard.panel_groups[0].metrics[0].metrics[0];
const { label, query_range } = stateCopy.dashboard.panel_groups[0].panels[0].metrics[0];
expect(label).toEqual(expectedLabel);
expect(query_range.length).toBeGreaterThan(0);
});
......@@ -39,23 +39,12 @@ describe('Monitoring mutations', () => {
expect(stateCopy.dashboard.panel_groups[0].panels[0].metrics.length).toEqual(1);
expect(stateCopy.dashboard.panel_groups[0].panels[1].metrics.length).toEqual(1);
});
it('assigns queries a metric id', () => {
it('assigns metrics a metric id', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].metricId).toEqual(
expect(stateCopy.dashboard.panel_groups[0].panels[0].metrics[0].metricId).toEqual(
'17_system_metrics_kubernetes_container_memory_average',
);
});
describe('dashboard endpoint', () => {
const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups;
it('aliases group panels to metrics for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
expect(stateCopy.dashboard.panel_groups[0].metrics[0]).toBeDefined();
});
it('aliases panel metrics to queries for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries).toBeDefined();
});
});
});
describe('RECEIVE_DEPLOYMENTS_DATA_SUCCESS', () => {
......
import { groupQueriesByChartInfo, normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils';
describe('groupQueriesByChartInfo', () => {
let input;
let output;
it('groups metrics with the same chart title and y_axis label', () => {
input = [
{ title: 'title', y_label: 'MB', queries: [{}] },
{ title: 'title', y_label: 'MB', queries: [{}] },
{ title: 'new title', y_label: 'MB', queries: [{}] },
];
output = [
{
title: 'title',
y_label: 'MB',
queries: [{ metricId: null }, { metricId: null }],
},
{ title: 'new title', y_label: 'MB', queries: [{ metricId: null }] },
];
expect(groupQueriesByChartInfo(input)).toEqual(output);
});
// Functionality associated with the /additional_metrics endpoint
it("associates a chart's stringified metric_id with the metric", () => {
input = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{}] }];
output = [{ id: 3, title: 'new title', y_label: 'MB', queries: [{ metricId: '3' }] }];
expect(groupQueriesByChartInfo(input)).toEqual(output);
});
// Functionality associated with the /metrics_dashboard endpoint
it('aliases a stringified metrics_id on the metric to the metricId key', () => {
input = [{ title: 'new title', y_label: 'MB', queries: [{ metric_id: 3 }] }];
output = [{ title: 'new title', y_label: 'MB', queries: [{ metricId: '3', metric_id: 3 }] }];
expect(groupQueriesByChartInfo(input)).toEqual(output);
});
});
import { normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils';
describe('normalizeMetric', () => {
[
......@@ -54,7 +14,7 @@ describe('normalizeMetric', () => {
},
].forEach(({ args, expected }) => {
it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => {
expect(normalizeMetric(...args)).toEqual({ metric_id: expected });
expect(normalizeMetric(...args)).toEqual({ metric_id: expected, metricId: expected });
});
});
});
......
......@@ -11,7 +11,7 @@ describe('Column component', () => {
columnChart = shallowMount(localVue.extend(ColumnChart), {
propsData: {
graphData: {
queries: [
metrics: [
{
x_label: 'Time',
y_label: 'Usage',
......
......@@ -105,22 +105,11 @@ export const graphDataPrometheusQuery = {
metrics: [
{
id: 'metric_a1',
metric_id: 2,
metricId: '2',
query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
unit: 'MB',
label: 'Total Consumption',
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
},
],
queries: [
{
metricId: null,
id: 'metric_a1',
metric_id: 2,
query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
unit: 'MB',
label: 'Total Consumption',
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
result: [
......@@ -140,24 +129,12 @@ export const graphDataPrometheusQueryRange = {
metrics: [
{
id: 'metric_a1',
metric_id: 2,
metricId: '2',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
unit: 'MB',
label: 'Total Consumption',
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
},
],
queries: [
{
metricId: '10',
id: 'metric_a1',
metric_id: 2,
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
unit: 'MB',
label: 'Total Consumption',
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
result: [
......@@ -176,8 +153,7 @@ export const graphDataPrometheusQueryRangeMultiTrack = {
weight: 3,
x_label: 'Status Code',
y_label: 'Time',
metrics: [],
queries: [
metrics: [
{
metricId: '1',
id: 'response_metrics_nginx_ingress_throughput_status_code',
......
......@@ -314,32 +314,32 @@ describe('isDateTimePickerInputValid', () => {
});
describe('graphDataValidatorForAnomalyValues', () => {
let oneQuery;
let threeQueries;
let fourQueries;
let oneMetric;
let threeMetrics;
let fourMetrics;
beforeEach(() => {
oneQuery = graphDataPrometheusQuery;
threeQueries = anomalyMockGraphData;
oneMetric = graphDataPrometheusQuery;
threeMetrics = anomalyMockGraphData;
const queries = [...threeQueries.queries];
queries.push(threeQueries.queries[0]);
fourQueries = {
const metrics = [...threeMetrics.metrics];
metrics.push(threeMetrics.metrics[0]);
fourMetrics = {
...anomalyMockGraphData,
queries,
metrics,
};
});
/*
* Anomaly charts can accept results for exactly 3 queries,
* Anomaly charts can accept results for exactly 3 metrics,
*/
it('validates passes with the right query format', () => {
expect(graphDataValidatorForAnomalyValues(threeQueries)).toBe(true);
expect(graphDataValidatorForAnomalyValues(threeMetrics)).toBe(true);
});
it('validation fails for wrong format, 1 metric', () => {
expect(graphDataValidatorForAnomalyValues(oneQuery)).toBe(false);
expect(graphDataValidatorForAnomalyValues(oneMetric)).toBe(false);
});
it('validation fails for wrong format, more than 3 metrics', () => {
expect(graphDataValidatorForAnomalyValues(fourQueries)).toBe(false);
expect(graphDataValidatorForAnomalyValues(fourMetrics)).toBe(false);
});
});
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