Commit c6f01133 authored by Miguel Rincon's avatar Miguel Rincon

Remove duplicated data in metrics store

As part of the an effort to ensure compatibility from different data
sources, the dashboard "normalized" data to ensure data will always
available.

Now that the compativblity is not needed thanks to
https://gitlab.com/gitlab-org/gitlab/merge_requests/15709,
the data sources are uniform again.

Objects in dashboard store have been renamed from

- dashboard[].metrics to dashboard[].panels
- panel[].queries to panel[].metrics

Other components that depend in this data have been updated, added
empty data checks so components work while the dahboard loads. Embed
component also considered.
parent 1d33ddd2
......@@ -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