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}) ...@@ -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