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}) ...@@ -29,7 +29,7 @@ const AREA_COLOR_RGBA = `rgba(${hexToRgb(AREA_COLOR).join(',')},${AREA_OPACITY})
* time series chart, the boundary band shows the normal * time series chart, the boundary band shows the normal
* range of values the metric should take. * 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. * "metric", "upper" limit and "lower" limit.
* *
* The upper and lower series are "stacked areas" visually * The upper and lower series are "stacked areas" visually
...@@ -62,10 +62,10 @@ export default { ...@@ -62,10 +62,10 @@ export default {
}, },
computed: { computed: {
series() { series() {
return this.graphData.queries.map(query => { return this.graphData.metrics.map(metric => {
const values = query.result[0] ? query.result[0].values : []; const values = metric.result && metric.result[0] ? metric.result[0].values : [];
return { return {
label: query.label, label: metric.label,
// NaN values may disrupt avg., max. & min. calculations in the legend, filter them out // NaN values may disrupt avg., max. & min. calculations in the legend, filter them out
data: values.filter(([, value]) => !Number.isNaN(value)), data: values.filter(([, value]) => !Number.isNaN(value)),
}; };
...@@ -83,7 +83,7 @@ export default { ...@@ -83,7 +83,7 @@ export default {
return min < 0 ? -min : 0; return min < 0 ? -min : 0;
}, },
metricData() { metricData() {
const originalMetricQuery = this.graphData.queries[0]; const originalMetricQuery = this.graphData.metrics[0];
const metricQuery = { ...originalMetricQuery }; const metricQuery = { ...originalMetricQuery };
metricQuery.result[0].values = metricQuery.result[0].values.map(([x, y]) => [ metricQuery.result[0].values = metricQuery.result[0].values.map(([x, y]) => [
...@@ -93,7 +93,7 @@ export default { ...@@ -93,7 +93,7 @@ export default {
return { return {
...this.graphData, ...this.graphData,
type: 'line-chart', type: 'line-chart',
queries: [metricQuery], metrics: [metricQuery],
}; };
}, },
metricSeriesConfig() { metricSeriesConfig() {
......
...@@ -32,8 +32,8 @@ export default { ...@@ -32,8 +32,8 @@ export default {
}, },
computed: { computed: {
chartData() { chartData() {
const queryData = this.graphData.queries.reduce((acc, query) => { const queryData = this.graphData.metrics.reduce((acc, query) => {
const series = makeDataSeries(query.result, { const series = makeDataSeries(query.result || [], {
name: this.formatLegendLabel(query), name: this.formatLegendLabel(query),
}); });
...@@ -45,13 +45,13 @@ export default { ...@@ -45,13 +45,13 @@ export default {
}; };
}, },
xAxisTitle() { xAxisTitle() {
return this.graphData.queries[0].result[0].x_label !== undefined return this.graphData.metrics[0].result[0].x_label !== undefined
? this.graphData.queries[0].result[0].x_label ? this.graphData.metrics[0].result[0].x_label
: ''; : '';
}, },
yAxisTitle() { yAxisTitle() {
return this.graphData.queries[0].result[0].y_label !== undefined return this.graphData.metrics[0].result[0].y_label !== undefined
? this.graphData.queries[0].result[0].y_label ? this.graphData.metrics[0].result[0].y_label
: ''; : '';
}, },
xAxisType() { xAxisType() {
......
...@@ -24,7 +24,7 @@ export default { ...@@ -24,7 +24,7 @@ export default {
}, },
computed: { computed: {
chartData() { chartData() {
return this.queries.result.reduce( return this.metrics.result.reduce(
(acc, result, i) => [...acc, ...result.values.map((value, j) => [i, j, value[1]])], (acc, result, i) => [...acc, ...result.values.map((value, j) => [i, j, value[1]])],
[], [],
); );
...@@ -36,7 +36,7 @@ export default { ...@@ -36,7 +36,7 @@ export default {
return this.graphData.y_label || ''; return this.graphData.y_label || '';
}, },
xAxisLabels() { xAxisLabels() {
return this.queries.result.map(res => Object.values(res.metric)[0]); return this.metrics.result.map(res => Object.values(res.metric)[0]);
}, },
yAxisLabels() { yAxisLabels() {
return this.result.values.map(val => { return this.result.values.map(val => {
...@@ -46,10 +46,10 @@ export default { ...@@ -46,10 +46,10 @@ export default {
}); });
}, },
result() { result() {
return this.queries.result[0]; return this.metrics.result[0];
}, },
queries() { metrics() {
return this.graphData.queries[0]; return this.graphData.metrics[0];
}, },
}, },
}; };
......
...@@ -17,7 +17,7 @@ export default { ...@@ -17,7 +17,7 @@ export default {
}, },
computed: { computed: {
queryInfo() { queryInfo() {
return this.graphData.queries[0]; return this.graphData.metrics[0];
}, },
engineeringNotation() { engineeringNotation() {
return `${roundOffFloat(this.queryInfo.result[0].value[1], 1)}${this.queryInfo.unit}`; return `${roundOffFloat(this.queryInfo.result[0].value[1], 1)}${this.queryInfo.unit}`;
......
...@@ -105,7 +105,7 @@ export default { ...@@ -105,7 +105,7 @@ export default {
// Transforms & supplements query data to render appropriate labels & styles // Transforms & supplements query data to render appropriate labels & styles
// Input: [{ queryAttributes1 }, { queryAttributes2 }] // Input: [{ queryAttributes1 }, { queryAttributes2 }]
// Output: [{ seriesAttributes1 }, { seriesAttributes2 }] // Output: [{ seriesAttributes1 }, { seriesAttributes2 }]
return this.graphData.queries.reduce((acc, query) => { return this.graphData.metrics.reduce((acc, query) => {
const { appearance } = query; const { appearance } = query;
const lineType = const lineType =
appearance && appearance.line && appearance.line.type appearance && appearance.line && appearance.line.type
...@@ -121,7 +121,7 @@ export default { ...@@ -121,7 +121,7 @@ export default {
? appearance.area.opacity ? appearance.area.opacity
: undefined, : undefined,
}; };
const series = makeDataSeries(query.result, { const series = makeDataSeries(query.result || [], {
name: this.formatLegendLabel(query), name: this.formatLegendLabel(query),
lineStyle: { lineStyle: {
type: lineType, type: lineType,
......
...@@ -250,14 +250,9 @@ export default { ...@@ -250,14 +250,9 @@ export default {
'setEndpoints', 'setEndpoints',
'setPanelGroupMetrics', 'setPanelGroupMetrics',
]), ]),
chartsWithData(charts) { updateMetrics(key, panels) {
return charts.filter(chart =>
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
updateMetrics(key, metrics) {
this.setPanelGroupMetrics({ this.setPanelGroupMetrics({
metrics, panels,
key, key,
}); });
}, },
...@@ -292,8 +287,13 @@ export default { ...@@ -292,8 +287,13 @@ export default {
submitCustomMetricsForm() { submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit(); this.$refs.customMetricsForm.submit();
}, },
chartsWithData(panels) {
return panels.filter(panel =>
panel.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
groupHasData(group) { groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0; return this.chartsWithData(group.panels).length > 0;
}, },
onDateTimePickerApply(timeWindowUrlParams) { onDateTimePickerApply(timeWindowUrlParams) {
return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href)); return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
...@@ -453,14 +453,14 @@ export default { ...@@ -453,14 +453,14 @@ export default {
:collapse-group="groupHasData(groupData)" :collapse-group="groupHasData(groupData)"
> >
<vue-draggable <vue-draggable
:value="groupData.metrics" :value="groupData.panels"
group="metrics-dashboard" group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }" :component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels" :disabled="!isRearrangingPanels"
@input="updateMetrics(groupData.key, $event)" @input="updateMetrics(groupData.key, $event)"
> >
<div <div
v-for="(graphData, graphIndex) in groupData.metrics" v-for="(graphData, graphIndex) in groupData.panels"
:key="`panel-type-${graphIndex}`" :key="`panel-type-${graphIndex}`"
class="col-12 col-lg-6 px-2 mb-2 draggable" class="col-12 col-lg-6 px-2 mb-2 draggable"
:class="{ 'draggable-enabled': isRearrangingPanels }" :class="{ 'draggable-enabled': isRearrangingPanels }"
...@@ -469,7 +469,7 @@ export default { ...@@ -469,7 +469,7 @@ export default {
<div <div
v-if="isRearrangingPanels" v-if="isRearrangingPanels"
class="draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end" 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')" <a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')"
><icon name="close" ><icon name="close"
......
...@@ -37,11 +37,14 @@ export default { ...@@ -37,11 +37,14 @@ export default {
computed: { computed: {
...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']), ...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() { charts() {
if (!this.dashboard || !this.dashboard.panel_groups) {
return [];
}
const groupWithMetrics = this.dashboard.panel_groups.find(group => const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)), group.panels.find(chart => this.chartHasData(chart)),
) || { metrics: [] }; ) || { panels: [] };
return groupWithMetrics.metrics.filter(chart => this.chartHasData(chart)); return groupWithMetrics.panels.filter(chart => this.chartHasData(chart));
}, },
isSingleChart() { isSingleChart() {
return this.charts.length === 1; return this.charts.length === 1;
......
...@@ -54,10 +54,14 @@ export default { ...@@ -54,10 +54,14 @@ export default {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData; return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
}, },
graphDataHasMetrics() { 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() { 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 yLabel = this.graphData.y_label;
const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
return chartData.reduce((csv, data) => { return chartData.reduce((csv, data) => {
...@@ -112,7 +116,7 @@ export default { ...@@ -112,7 +116,7 @@ export default {
:graph-data="graphData" :graph-data="graphData"
:deployment-data="deploymentData" :deployment-data="deploymentData"
:project-path="projectPath" :project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.queries)" :thresholds="getGraphAlertValues(graphData.metrics)"
group-id="panel-type-chart" group-id="panel-type-chart"
> >
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
...@@ -120,8 +124,8 @@ export default { ...@@ -120,8 +124,8 @@ export default {
v-if="alertWidgetAvailable && graphData" v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`" :modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint" :alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries" :relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.queries)" :alerts-to-manage="getGraphAlerts(graphData.metrics)"
@setAlerts="setAlerts" @setAlerts="setAlerts"
/> />
<gl-dropdown <gl-dropdown
......
import Vue from 'vue'; import Vue from 'vue';
import { slugify } from '~/lib/utils/text_utility'; import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types'; 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 { export default {
[types.REQUEST_METRICS_DATA](state) { [types.REQUEST_METRICS_DATA](state) {
...@@ -13,28 +17,18 @@ export default { ...@@ -13,28 +17,18 @@ export default {
[types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) { [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
state.dashboard.panel_groups = groupData.map((group, i) => { state.dashboard.panel_groups = groupData.map((group, i) => {
const key = `${slugify(group.group || 'default')}-${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 // each panel has metric information that needs to be normalized
panels = panels.map(panel => ({ panels = panels.map(panel => ({
...panel, ...panel,
metrics: normalizePanel(panel), metrics: normalizePanelMetrics(panel.metrics, panel.y_label),
}));
// 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,
})); }));
return { return {
...group, ...group,
panels, panels,
key, key,
metrics: normalizeMetrics(metrics),
}; };
}); });
...@@ -58,6 +52,7 @@ export default { ...@@ -58,6 +52,7 @@ export default {
[types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) { [types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) {
state.environments = []; state.environments = [];
}, },
[types.SET_QUERY_RESULT](state, { metricId, result }) { [types.SET_QUERY_RESULT](state, { metricId, result }) {
if (!metricId || !result || result.length === 0) { if (!metricId || !result || result.length === 0) {
return; return;
...@@ -65,14 +60,17 @@ export default { ...@@ -65,14 +60,17 @@ export default {
state.showEmptyState = false; state.showEmptyState = false;
/**
* Search the dashboard state for a matching id
*/
state.dashboard.panel_groups.forEach(group => { state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => { group.panels.forEach(panel => {
metric.queries.forEach(query => { panel.metrics.forEach(metric => {
if (query.metric_id === metricId) { if (metric.metric_id === metricId) {
state.metricsWithData.push(metricId); state.metricsWithData.push(metricId);
// ensure dates/numbers are correctly formatted for charts // ensure dates/numbers are correctly formatted for charts
const normalizedResults = result.map(normalizeQueryResult); 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 { ...@@ -101,6 +99,6 @@ export default {
}, },
[types.SET_PANEL_GROUP_METRICS](state, payload) { [types.SET_PANEL_GROUP_METRICS](state, payload) {
const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key); const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key);
panelGroup.metrics = payload.metrics; panelGroup.panels = payload.panels;
}, },
}; };
import _ from 'underscore'; 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}`; 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. * 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 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 * This is hopefully a temporary solution until BE processes metrics before passing to fE
* @param {Object} metric - metric * @param {Object} metric - metric
* @returns {Object} - normalized metric with a uniqueID * @returns {Object} - normalized metric with a uniqueID
*/ */
export const normalizeMetric = (metric = {}) => export const normalizeMetric = (metric = {}) =>
_.omit( _.omit(
{ {
...metric, ...metric,
metric_id: uniqMetricsId(metric), metric_id: uniqMetricsId(metric),
metricId: uniqMetricsId(metric),
}, },
'id', 'id',
); );
...@@ -93,6 +31,11 @@ export const normalizeQueryResult = timeSeries => { ...@@ -93,6 +31,11 @@ export const normalizeQueryResult = timeSeries => {
Number(value), 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) { } else if (timeSeries.value) {
normalizedResult = { normalizedResult = {
...timeSeries, ...timeSeries,
...@@ -102,21 +45,3 @@ export const normalizeQueryResult = timeSeries => { ...@@ -102,21 +45,3 @@ export const normalizeQueryResult = timeSeries => {
return normalizedResult; 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 ...@@ -72,10 +72,9 @@ export const ISODateToString = date => dateformat(date, dateFormats.dateTimePick
*/ */
export const graphDataValidatorForValues = (isValues, graphData) => { export const graphDataValidatorForValues = (isValues, graphData) => {
const responseValueKeyName = isValues ? 'value' : 'values'; const responseValueKeyName = isValues ? 'value' : 'values';
return ( return (
Array.isArray(graphData.queries) && Array.isArray(graphData.metrics) &&
graphData.queries.filter(query => { graphData.metrics.filter(query => {
if (Array.isArray(query.result)) { if (Array.isArray(query.result)) {
return ( return (
query.result.filter(res => Array.isArray(res[responseValueKeyName])).length === query.result.filter(res => Array.isArray(res[responseValueKeyName])).length ===
...@@ -83,7 +82,7 @@ export const graphDataValidatorForValues = (isValues, graphData) => { ...@@ -83,7 +82,7 @@ export const graphDataValidatorForValues = (isValues, graphData) => {
); );
} }
return false; return false;
}).length === graphData.queries.length }).length === graphData.metrics.filter(query => query.result).length
); );
}; };
...@@ -131,7 +130,7 @@ export const downloadCSVOptions = title => { ...@@ -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. * value validations from graphDataValidatorForValues.
* @param {Object} isValues * @param {Object} isValues
* @param {Object} graphData the graph data response from a prometheus request * @param {Object} graphData the graph data response from a prometheus request
...@@ -140,8 +139,8 @@ export const downloadCSVOptions = title => { ...@@ -140,8 +139,8 @@ export const downloadCSVOptions = title => {
export const graphDataValidatorForAnomalyValues = graphData => { export const graphDataValidatorForAnomalyValues = graphData => {
const anomalySeriesCount = 3; // metric, upper, lower const anomalySeriesCount = 3; // metric, upper, lower
return ( return (
graphData.queries && graphData.metrics &&
graphData.queries.length === anomalySeriesCount && graphData.metrics.length === anomalySeriesCount &&
graphDataValidatorForValues(false, graphData) graphDataValidatorForValues(false, graphData)
); );
}; };
......
...@@ -48,7 +48,7 @@ describe('Time series component', () => { ...@@ -48,7 +48,7 @@ describe('Time series component', () => {
// Mock data contains 2 panels, pick the first one // Mock data contains 2 panels, pick the first one
store.commit(`monitoringDashboard/${types.SET_QUERY_RESULT}`, mockedQueryResultPayload); 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) => makeTimeSeriesChart = (graphData, type) =>
shallowMount(TimeSeries, { shallowMount(TimeSeries, {
...@@ -235,7 +235,7 @@ describe('Time series component', () => { ...@@ -235,7 +235,7 @@ describe('Time series component', () => {
}); });
it('utilizes all data points', () => { 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(chartData.length).toBe(1);
expect(seriesData().data.length).toBe(values.length); expect(seriesData().data.length).toBe(values.length);
......
...@@ -17,8 +17,8 @@ const mockProjectPath = `${TEST_HOST}${mockProjectDir}`; ...@@ -17,8 +17,8 @@ const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent
const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => { const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
const queries = anomalyMockResultValues[datasetName].map((values, index) => ({ const metrics = anomalyMockResultValues[datasetName].map((values, index) => ({
...template.queries[index], ...template.metrics[index],
result: [ result: [
{ {
metrics: {}, metrics: {},
...@@ -26,7 +26,7 @@ const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => { ...@@ -26,7 +26,7 @@ const makeAnomalyGraphData = (datasetName, template = anomalyMockGraphData) => {
}, },
], ],
})); }));
return { ...template, queries }; return { ...template, metrics };
}; };
describe('Anomaly chart component', () => { describe('Anomaly chart component', () => {
...@@ -67,19 +67,19 @@ describe('Anomaly chart component', () => { ...@@ -67,19 +67,19 @@ describe('Anomaly chart component', () => {
describe('graph-data', () => { describe('graph-data', () => {
it('receives a single "metric" series', () => { it('receives a single "metric" series', () => {
const { graphData } = getTimeSeriesProps(); const { graphData } = getTimeSeriesProps();
expect(graphData.queries.length).toBe(1); expect(graphData.metrics.length).toBe(1);
}); });
it('receives "metric" with all data', () => { it('receives "metric" with all data', () => {
const { graphData } = getTimeSeriesProps(); const { graphData } = getTimeSeriesProps();
const query = graphData.queries[0]; const query = graphData.metrics[0];
const expectedQuery = makeAnomalyGraphData(dataSetName).queries[0]; const expectedQuery = makeAnomalyGraphData(dataSetName).metrics[0];
expect(query).toEqual(expectedQuery); expect(query).toEqual(expectedQuery);
}); });
it('receives the "metric" results', () => { it('receives the "metric" results', () => {
const { graphData } = getTimeSeriesProps(); const { graphData } = getTimeSeriesProps();
const { result } = graphData.queries[0]; const { result } = graphData.metrics[0];
const { values } = result[0]; const { values } = result[0];
const [metricDataset] = dataSet; const [metricDataset] = dataSet;
expect(values).toEqual(expect.any(Array)); expect(values).toEqual(expect.any(Array));
...@@ -266,12 +266,12 @@ describe('Anomaly chart component', () => { ...@@ -266,12 +266,12 @@ describe('Anomaly chart component', () => {
describe('graph-data', () => { describe('graph-data', () => {
it('receives a single "metric" series', () => { it('receives a single "metric" series', () => {
const { graphData } = getTimeSeriesProps(); 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', () => { it('receives "metric" results and applies the offset to them', () => {
const { graphData } = getTimeSeriesProps(); const { graphData } = getTimeSeriesProps();
const { result } = graphData.queries[0]; const { result } = graphData.metrics[0];
const { values } = result[0]; const { values } = result[0];
const [metricDataset] = dataSet; const [metricDataset] = dataSet;
expect(values).toEqual(expect.any(Array)); expect(values).toEqual(expect.any(Array));
......
...@@ -62,7 +62,7 @@ describe('Embed', () => { ...@@ -62,7 +62,7 @@ describe('Embed', () => {
describe('metrics are available', () => { describe('metrics are available', () => {
beforeEach(() => { beforeEach(() => {
store.state.monitoringDashboard.dashboard.panel_groups = groups; 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; store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent(); mountComponent();
......
...@@ -42,38 +42,34 @@ export const metrics = [ ...@@ -42,38 +42,34 @@ export const metrics = [
}, },
]; ];
const queries = [ const result = [
{ {
result: [ values: [
{ ['Mon', 1220],
values: [ ['Tue', 932],
['Mon', 1220], ['Wed', 901],
['Tue', 932], ['Thu', 934],
['Wed', 901], ['Fri', 1290],
['Thu', 934], ['Sat', 1330],
['Fri', 1290], ['Sun', 1320],
['Sat', 1330],
['Sun', 1320],
],
},
], ],
}, },
]; ];
export const metricsData = [ export const metricsData = [
{ {
queries,
metrics: [ metrics: [
{ {
metric_id: 15, metric_id: 15,
result,
}, },
], ],
}, },
{ {
queries,
metrics: [ metrics: [
{ {
metric_id: 16, metric_id: 16,
result,
}, },
], ],
}, },
......
...@@ -110,9 +110,6 @@ export const anomalyMockGraphData = { ...@@ -110,9 +110,6 @@ export const anomalyMockGraphData = {
type: 'anomaly-chart', type: 'anomaly-chart',
weight: 3, weight: 3,
metrics: [ metrics: [
// Not used
],
queries: [
{ {
metricId: '90', metricId: '90',
id: 'metric', id: 'metric',
......
...@@ -33,7 +33,7 @@ describe('Panel Type component', () => { ...@@ -33,7 +33,7 @@ describe('Panel Type component', () => {
let glEmptyChart; let glEmptyChart;
// Deep clone object before modifying // Deep clone object before modifying
const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange)); const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
graphDataNoResult.queries[0].result = []; graphDataNoResult.metrics[0].result = [];
beforeEach(() => { beforeEach(() => {
panelType = shallowMount(PanelType, { panelType = shallowMount(PanelType, {
...@@ -143,7 +143,7 @@ describe('Panel Type component', () => { ...@@ -143,7 +143,7 @@ describe('Panel Type component', () => {
describe('csvText', () => { describe('csvText', () => {
it('converts metrics data from json to csv', () => { it('converts metrics data from json to csv', () => {
const header = `timestamp,${graphDataPrometheusQueryRange.y_label}`; 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 firstRow = `${data[0][0]},${data[0][1]}`;
const secondRow = `${data[1][0]},${data[1][1]}`; const secondRow = `${data[1][0]},${data[1][1]}`;
......
...@@ -27,7 +27,7 @@ describe('Monitoring mutations', () => { ...@@ -27,7 +27,7 @@ describe('Monitoring mutations', () => {
it('normalizes values', () => { it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedLabel = 'Pod average'; 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(label).toEqual(expectedLabel);
expect(query_range.length).toBeGreaterThan(0); expect(query_range.length).toBeGreaterThan(0);
}); });
...@@ -39,23 +39,12 @@ describe('Monitoring mutations', () => { ...@@ -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[0].metrics.length).toEqual(1);
expect(stateCopy.dashboard.panel_groups[0].panels[1].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); 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', '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', () => { describe('RECEIVE_DEPLOYMENTS_DATA_SUCCESS', () => {
......
import { groupQueriesByChartInfo, normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils'; import { 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);
});
});
describe('normalizeMetric', () => { describe('normalizeMetric', () => {
[ [
...@@ -54,7 +14,7 @@ describe('normalizeMetric', () => { ...@@ -54,7 +14,7 @@ describe('normalizeMetric', () => {
}, },
].forEach(({ args, expected }) => { ].forEach(({ args, expected }) => {
it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => { 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', () => { ...@@ -11,7 +11,7 @@ describe('Column component', () => {
columnChart = shallowMount(localVue.extend(ColumnChart), { columnChart = shallowMount(localVue.extend(ColumnChart), {
propsData: { propsData: {
graphData: { graphData: {
queries: [ metrics: [
{ {
x_label: 'Time', x_label: 'Time',
y_label: 'Usage', y_label: 'Usage',
......
...@@ -105,22 +105,11 @@ export const graphDataPrometheusQuery = { ...@@ -105,22 +105,11 @@ export const graphDataPrometheusQuery = {
metrics: [ metrics: [
{ {
id: 'metric_a1', id: 'metric_a1',
metric_id: 2, metricId: '2',
query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024', query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
unit: 'MB', unit: 'MB',
label: 'Total Consumption', 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, metric_id: 2,
query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
unit: 'MB',
label: 'Total Consumption',
prometheus_endpoint_path: 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', '/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: [ result: [
...@@ -140,24 +129,12 @@ export const graphDataPrometheusQueryRange = { ...@@ -140,24 +129,12 @@ export const graphDataPrometheusQueryRange = {
metrics: [ metrics: [
{ {
id: 'metric_a1', id: 'metric_a1',
metric_id: 2, metricId: '2',
query_range: 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', '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', unit: 'MB',
label: 'Total Consumption', 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, 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: 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', '/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: [ result: [
...@@ -176,8 +153,7 @@ export const graphDataPrometheusQueryRangeMultiTrack = { ...@@ -176,8 +153,7 @@ export const graphDataPrometheusQueryRangeMultiTrack = {
weight: 3, weight: 3,
x_label: 'Status Code', x_label: 'Status Code',
y_label: 'Time', y_label: 'Time',
metrics: [], metrics: [
queries: [
{ {
metricId: '1', metricId: '1',
id: 'response_metrics_nginx_ingress_throughput_status_code', id: 'response_metrics_nginx_ingress_throughput_status_code',
......
...@@ -314,32 +314,32 @@ describe('isDateTimePickerInputValid', () => { ...@@ -314,32 +314,32 @@ describe('isDateTimePickerInputValid', () => {
}); });
describe('graphDataValidatorForAnomalyValues', () => { describe('graphDataValidatorForAnomalyValues', () => {
let oneQuery; let oneMetric;
let threeQueries; let threeMetrics;
let fourQueries; let fourMetrics;
beforeEach(() => { beforeEach(() => {
oneQuery = graphDataPrometheusQuery; oneMetric = graphDataPrometheusQuery;
threeQueries = anomalyMockGraphData; threeMetrics = anomalyMockGraphData;
const queries = [...threeQueries.queries]; const metrics = [...threeMetrics.metrics];
queries.push(threeQueries.queries[0]); metrics.push(threeMetrics.metrics[0]);
fourQueries = { fourMetrics = {
...anomalyMockGraphData, ...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', () => { 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', () => { 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', () => { 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