Commit f040647e authored by Miguel Rincon's avatar Miguel Rincon

Convert dashobard keys to camel case

When the action response is obtained, convert it before
it arrives to the store.

Some keys are ignore from the conversion to reduce scope of changes.

Update mock_data to reduce ducplication.
parent a5606156
...@@ -522,7 +522,7 @@ export default { ...@@ -522,7 +522,7 @@ export default {
<div v-if="!showEmptyState"> <div v-if="!showEmptyState">
<graph-group <graph-group
v-for="(groupData, index) in dashboard.panel_groups" v-for="(groupData, index) in dashboard.panelGroups"
:key="`${groupData.group}.${groupData.priority}`" :key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group" :name="groupData.group"
:show-panels="showPanels" :show-panels="showPanels"
......
...@@ -28,10 +28,10 @@ export default { ...@@ -28,10 +28,10 @@ export default {
...mapState('monitoringDashboard', ['dashboard']), ...mapState('monitoringDashboard', ['dashboard']),
...mapGetters('monitoringDashboard', ['metricsWithData']), ...mapGetters('monitoringDashboard', ['metricsWithData']),
charts() { charts() {
if (!this.dashboard || !this.dashboard.panel_groups) { if (!this.dashboard || !this.dashboard.panelGroups) {
return []; return [];
} }
const groupWithMetrics = this.dashboard.panel_groups.find(group => const groupWithMetrics = this.dashboard.panelGroups.find(group =>
group.panels.find(chart => this.chartHasData(chart)), group.panels.find(chart => this.chartHasData(chart)),
) || { panels: [] }; ) || { panels: [] };
......
...@@ -51,9 +51,11 @@ export const requestMetricsDashboard = ({ commit }) => { ...@@ -51,9 +51,11 @@ export const requestMetricsDashboard = ({ commit }) => {
commit(types.REQUEST_METRICS_DATA); commit(types.REQUEST_METRICS_DATA);
}; };
export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response, params }) => { export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response, params }) => {
commit(types.SET_ALL_DASHBOARDS, response.all_dashboards); const { all_dashboards, dashboard, metrics_data } = response;
commit(types.RECEIVE_METRICS_DATA_SUCCESS, response.dashboard);
commit(types.SET_ENDPOINTS, convertObjectPropsToCamelCase(response.metrics_data)); commit(types.SET_ALL_DASHBOARDS, all_dashboards);
commit(types.RECEIVE_METRICS_DATA_SUCCESS, dashboard);
commit(types.SET_ENDPOINTS, convertObjectPropsToCamelCase(metrics_data));
return dispatch('fetchPrometheusMetrics', params); return dispatch('fetchPrometheusMetrics', params);
}; };
...@@ -149,16 +151,16 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { ...@@ -149,16 +151,16 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
step, step,
}; };
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metric_id }); commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
return fetchPrometheusResult(metric.prometheus_endpoint_path, queryParams) return fetchPrometheusResult(metric.prometheusEndpointPath, queryParams)
.then(result => { .then(result => {
commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metric_id, result }); commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, result });
}) })
.catch(error => { .catch(error => {
Sentry.captureException(error); Sentry.captureException(error);
commit(types.RECEIVE_METRIC_RESULT_FAILURE, { metricId: metric.metric_id, error }); commit(types.RECEIVE_METRIC_RESULT_FAILURE, { metricId: metric.metricId, error });
// Continue to throw error so the dashboard can notify using createFlash // Continue to throw error so the dashboard can notify using createFlash
throw error; throw error;
}); });
...@@ -168,7 +170,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, par ...@@ -168,7 +170,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, par
commit(types.REQUEST_METRICS_DATA); commit(types.REQUEST_METRICS_DATA);
const promises = []; const promises = [];
state.dashboard.panel_groups.forEach(group => { state.dashboard.panelGroups.forEach(group => {
group.panels.forEach(panel => { group.panels.forEach(panel => {
panel.metrics.forEach(metric => { panel.metrics.forEach(metric => {
promises.push(dispatch('fetchPrometheusMetric', { metric, params })); promises.push(dispatch('fetchPrometheusMetric', { metric, params }));
......
...@@ -11,7 +11,7 @@ const metricsIdsInPanel = panel => ...@@ -11,7 +11,7 @@ const metricsIdsInPanel = panel =>
* states in all the metric in the dashboard or group. * states in all the metric in the dashboard or group.
*/ */
export const getMetricStates = state => groupKey => { export const getMetricStates = state => groupKey => {
let groups = state.dashboard.panel_groups; let groups = state.dashboard.panelGroups;
if (groupKey) { if (groupKey) {
groups = groups.filter(group => group.key === groupKey); groups = groups.filter(group => group.key === groupKey);
} }
...@@ -43,7 +43,7 @@ export const getMetricStates = state => groupKey => { ...@@ -43,7 +43,7 @@ export const getMetricStates = state => groupKey => {
* filtered by group key. * filtered by group key.
*/ */
export const metricsWithData = state => groupKey => { export const metricsWithData = state => groupKey => {
let groups = state.dashboard.panel_groups; let groups = state.dashboard.panelGroups;
if (groupKey) { if (groupKey) {
groups = groups.filter(group => group.key === groupKey); groups = groups.filter(group => group.key === groupKey);
} }
......
import Vue from 'vue'; import Vue from 'vue';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { normalizeMetric, normalizeQueryResult } from './utils'; import { mapToDashboardViewModel, normalizeQueryResult } from './utils';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils'; import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
import { metricStates } from '../constants'; import { metricStates } from '../constants';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
const normalizePanelMetrics = (metrics, defaultLabel) =>
metrics.map(metric => ({
...normalizeMetric(metric),
label: metric.label || defaultLabel,
}));
/** /**
* Locate and return a metric in the dashboard by its id * Locate and return a metric in the dashboard by its id
* as generated by `uniqMetricsId()`. * as generated by `uniqMetricsId()`.
...@@ -21,10 +14,10 @@ const normalizePanelMetrics = (metrics, defaultLabel) => ...@@ -21,10 +14,10 @@ const normalizePanelMetrics = (metrics, defaultLabel) =>
*/ */
const findMetricInDashboard = (metricId, dashboard) => { const findMetricInDashboard = (metricId, dashboard) => {
let res = null; let res = null;
dashboard.panel_groups.forEach(group => { dashboard.panelGroups.forEach(group => {
group.panels.forEach(panel => { group.panels.forEach(panel => {
panel.metrics.forEach(metric => { panel.metrics.forEach(metric => {
if (metric.metric_id === metricId) { if (metric.metricId === metricId) {
res = metric; res = metric;
} }
}); });
...@@ -86,27 +79,9 @@ export default { ...@@ -86,27 +79,9 @@ export default {
state.showEmptyState = true; state.showEmptyState = true;
}, },
[types.RECEIVE_METRICS_DATA_SUCCESS](state, dashboard) { [types.RECEIVE_METRICS_DATA_SUCCESS](state, dashboard) {
state.dashboard = { state.dashboard = mapToDashboardViewModel(dashboard);
...dashboard,
panel_groups: dashboard.panel_groups.map((group, i) => {
const key = `${slugify(group.group || 'default')}-${i}`;
let { panels = [] } = group;
// each panel has metric information that needs to be normalized
panels = panels.map(panel => ({
...panel,
metrics: normalizePanelMetrics(panel.metrics, panel.y_label),
}));
return {
...group,
panels,
key,
};
}),
};
if (!state.dashboard.panel_groups.length) { if (!state.dashboard.panelGroups.length) {
state.emptyState = 'noData'; state.emptyState = 'noData';
} }
}, },
...@@ -206,7 +181,7 @@ export default { ...@@ -206,7 +181,7 @@ export default {
state.showErrorBanner = enabled; state.showErrorBanner = enabled;
}, },
[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.panelGroups.find(pg => payload.key === pg.key);
panelGroup.panels = payload.panels; panelGroup.panels = payload.panels;
}, },
[types.SET_ENVIRONMENTS_FILTER](state, searchTerm) { [types.SET_ENVIRONMENTS_FILTER](state, searchTerm) {
......
...@@ -15,7 +15,7 @@ export default () => ({ ...@@ -15,7 +15,7 @@ export default () => ({
showEmptyState: true, showEmptyState: true,
showErrorBanner: true, showErrorBanner: true,
dashboard: { dashboard: {
panel_groups: [], panelGroups: [],
}, },
allDashboards: [], allDashboards: [],
......
import { omit } from 'lodash'; import { slugify } from '~/lib/utils/text_utility';
import createGqClient, { fetchPolicies } from '~/lib/graphql'; import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
...@@ -9,6 +9,13 @@ export const gqClient = createGqClient( ...@@ -9,6 +9,13 @@ export const gqClient = createGqClient(
}, },
); );
/**
* 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 uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`; export const uniqMetricsId = metric => `${metric.metric_id}_${metric.id}`;
/** /**
...@@ -41,22 +48,75 @@ export const parseEnvironmentsResponse = (response = [], projectPath) => ...@@ -41,22 +48,75 @@ export const parseEnvironmentsResponse = (response = [], projectPath) =>
}); });
/** /**
* Metrics loaded from project-defined dashboards do not have a metric_id. * Maps metrics to its view model
* 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 function difers from other in that is maps all
* @param {Object} metric - metric * non-define properties as-is to the object. This is not
* @returns {Object} - normalized metric with a uniqueID * advisable as it could lead to unexpected side-effects.
*
* Related issue:
* https://gitlab.com/gitlab-org/gitlab/issues/207198
*
* @param {Array} metrics - Array of prometheus metrics
* @param {String} defaultLabel - Default label for metrics
* @returns {Object}
*/ */
const mapToMetricsViewModel = (metrics, defaultLabel) =>
metrics.map(({ label, id, metric_id, query_range, prometheus_endpoint_path, ...metric }) => ({
label: label || defaultLabel,
queryRange: query_range,
prometheusEndpointPath: prometheus_endpoint_path,
metricId: uniqMetricsId({ metric_id, id }),
export const normalizeMetric = (metric = {}) => // `metric_id` is used by embed.vue, keeping this duplicated.
omit( // https://gitlab.com/gitlab-org/gitlab/issues/37492
{ metric_id: uniqMetricsId({ metric_id, id }),
...metric, ...metric,
metric_id: uniqMetricsId(metric), }));
metricId: uniqMetricsId(metric),
}, /**
'id', * Maps a metrics panel to its view model
); *
* @param {Object} panel - Metrics panel
* @returns {Object}
*/
const mapToPanelViewModel = ({ title = '', type, y_label, metrics = [] }) => {
return {
title,
type,
y_label,
metrics: mapToMetricsViewModel(metrics, y_label),
};
};
/**
* Maps a metrics panel group to its view model
*
* @param {Object} panelGroup - Panel Group
* @returns {Object}
*/
const mapToPanelGroupViewModel = ({ group = '', panels = [] }, i) => {
return {
key: `${slugify(group || 'default')}-${i}`,
group,
panels: panels.map(mapToPanelViewModel),
};
};
/**
* Maps a dashboard json object to its view model
*
* @param {Object} dashboard - Dashboard object
* @param {String} dashboard.dashboard - Dashboard name object
* @param {Array} dashboard.panel_groups - Panel groups array
* @returns {Object}
*/
export const mapToDashboardViewModel = ({ dashboard = '', panel_groups = [] }) => {
return {
dashboard,
panelGroups: panel_groups.map(mapToPanelGroupViewModel),
};
};
export const normalizeQueryResult = timeSeries => { export const normalizeQueryResult = timeSeries => {
let normalizedResult = {}; let normalizedResult = {};
......
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
/** /**
* This method is used to validate if the graph data format for a chart component * This method is used to validate if the graph data format for a chart component
* that needs a time series as a response from a prometheus query (query_range) is * that needs a time series as a response from a prometheus query (queryRange) is
* of a valid format or not. * of a valid format or not.
* @param {Object} graphData the graph data response from a prometheus request * @param {Object} graphData the graph data response from a prometheus request
* @returns {boolean} whether the graphData format is correct * @returns {boolean} whether the graphData format is correct
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
deploymentData, deploymentData,
metricsDashboardPayload, metricsDashboardPayload,
mockedQueryResultPayload, mockedQueryResultPayload,
metricsDashboardViewModel,
mockProjectDir, mockProjectDir,
mockHost, mockHost,
} from '../../mock_data'; } from '../../mock_data';
...@@ -65,7 +66,7 @@ describe('Time series component', () => { ...@@ -65,7 +66,7 @@ describe('Time series component', () => {
); );
// Pick the second panel group and the first panel in it // Pick the second panel group and the first panel in it
[mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; [mockGraphData] = store.state.monitoringDashboard.dashboard.panelGroups[0].panels;
}); });
describe('general functions', () => { describe('general functions', () => {
...@@ -188,7 +189,7 @@ describe('Time series component', () => { ...@@ -188,7 +189,7 @@ describe('Time series component', () => {
}); });
it('formats tooltip content', () => { it('formats tooltip content', () => {
const name = 'Pod average'; const name = 'Total';
const value = '5.556'; const value = '5.556';
const dataIndex = 0; const dataIndex = 0;
const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
...@@ -439,7 +440,7 @@ describe('Time series component', () => { ...@@ -439,7 +440,7 @@ describe('Time series component', () => {
it('constructs a label for the chart y-axis', () => { it('constructs a label for the chart y-axis', () => {
const { yAxis } = getChartOptions(); const { yAxis } = getChartOptions();
expect(yAxis[0].name).toBe('Memory Used per Pod'); expect(yAxis[0].name).toBe('Total Memory Used');
}); });
}); });
}); });
...@@ -535,46 +536,22 @@ describe('Time series component', () => { ...@@ -535,46 +536,22 @@ describe('Time series component', () => {
}); });
describe('with multiple time series', () => { describe('with multiple time series', () => {
const mockedResultMultipleSeries = []; describe('General functions', () => {
const [, , panelData] = metricsDashboardPayload.panel_groups[1].panels; let timeSeriesChart;
for (let i = 0; i < panelData.metrics.length; i += 1) {
mockedResultMultipleSeries.push(cloneDeep(mockedQueryResultPayload));
mockedResultMultipleSeries[
i
].metricId = `${panelData.metrics[i].metric_id}_${panelData.metrics[i].id}`;
}
beforeEach(() => {
setTestTimeout(1000);
beforeEach(done => {
store = createStore(); store = createStore();
const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]);
store.commit( graphData.metrics.forEach(metric =>
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, Object.assign(metric, { result: mockedQueryResultPayload.result }),
metricsDashboardPayload,
);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
// Mock data contains the metric_id for a multiple time series panel
for (let i = 0; i < panelData.metrics.length; i += 1) {
store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedResultMultipleSeries[i],
); );
}
// Pick the second panel group and the second panel in it timeSeriesChart = makeTimeSeriesChart(graphData, 'area-chart');
[, , mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; timeSeriesChart.vm.$nextTick(done);
}); });
describe('General functions', () => { afterEach(() => {
let timeSeriesChart; timeSeriesChart.destroy();
beforeEach(done => {
timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
timeSeriesChart.vm.$nextTick(done);
}); });
describe('computed', () => { describe('computed', () => {
......
...@@ -17,12 +17,13 @@ import { setupComponentStore, propsData } from '../init_utils'; ...@@ -17,12 +17,13 @@ import { setupComponentStore, propsData } from '../init_utils';
import { import {
metricsDashboardPayload, metricsDashboardPayload,
mockedQueryResultPayload, mockedQueryResultPayload,
metricsDashboardViewModel,
environmentData, environmentData,
dashboardGitResponse, dashboardGitResponse,
} from '../mock_data'; } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
const expectedPanelCount = 3; const expectedPanelCount = 4;
describe('Dashboard', () => { describe('Dashboard', () => {
let store; let store;
...@@ -366,7 +367,7 @@ describe('Dashboard', () => { ...@@ -366,7 +367,7 @@ describe('Dashboard', () => {
it('metrics can be swapped', () => { it('metrics can be swapped', () => {
const firstDraggable = findDraggables().at(0); const firstDraggable = findDraggables().at(0);
const mockMetrics = [...metricsDashboardPayload.panel_groups[1].panels]; const mockMetrics = [...metricsDashboardViewModel.panelGroups[0].panels];
const firstTitle = mockMetrics[0].title; const firstTitle = mockMetrics[0].title;
const secondTitle = mockMetrics[1].title; const secondTitle = mockMetrics[1].title;
...@@ -376,7 +377,7 @@ describe('Dashboard', () => { ...@@ -376,7 +377,7 @@ describe('Dashboard', () => {
firstDraggable.vm.$emit('input', mockMetrics); firstDraggable.vm.$emit('input', mockMetrics);
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
const { panels } = wrapper.vm.dashboard.panel_groups[1]; const { panels } = wrapper.vm.dashboard.panelGroups[0];
expect(panels[1].title).toEqual(firstTitle); expect(panels[1].title).toEqual(firstTitle);
expect(panels[0].title).toEqual(secondTitle); expect(panels[0].title).toEqual(secondTitle);
......
...@@ -69,8 +69,8 @@ describe('Embed', () => { ...@@ -69,8 +69,8 @@ describe('Embed', () => {
describe('metrics are available', () => { describe('metrics are available', () => {
beforeEach(() => { beforeEach(() => {
store.state.monitoringDashboard.dashboard.panel_groups = groups; store.state.monitoringDashboard.dashboard.panelGroups = groups;
store.state.monitoringDashboard.dashboard.panel_groups[0].panels = metricsData; store.state.monitoringDashboard.dashboard.panelGroups[0].panels = metricsData;
metricsWithDataGetter.mockReturnValue(metricsWithData); metricsWithDataGetter.mockReturnValue(metricsWithData);
......
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
// This import path needs to be relative for now because this mock data is used in // This import path needs to be relative for now because this mock data is used in
// Karma specs too, where the helpers/test_constants alias can not be resolved // Karma specs too, where the helpers/test_constants alias can not be resolved
import { TEST_HOST } from '../helpers/test_constants'; import { TEST_HOST } from '../helpers/test_constants';
...@@ -246,7 +248,7 @@ export const mockedEmptyResult = { ...@@ -246,7 +248,7 @@ export const mockedEmptyResult = {
}; };
export const mockedQueryResultPayload = { export const mockedQueryResultPayload = {
metricId: '17_system_metrics_kubernetes_container_memory_average', metricId: '12_system_metrics_kubernetes_container_memory_total',
result: [ result: [
{ {
metric: {}, metric: {},
...@@ -378,8 +380,7 @@ export const environmentData = [ ...@@ -378,8 +380,7 @@ export const environmentData = [
}, },
].concat(extraEnvironmentData); ].concat(extraEnvironmentData);
export const metricsDashboardResponse = { export const metricsDashboardPayload = {
dashboard: {
dashboard: 'Environment metrics', dashboard: 'Environment metrics',
priority: 1, priority: 1,
panel_groups: [ panel_groups: [
...@@ -436,83 +437,6 @@ export const metricsDashboardResponse = { ...@@ -436,83 +437,6 @@ export const metricsDashboardResponse = {
}, },
], ],
}, },
],
},
],
},
status: 'success',
};
export const metricsDashboardPayload = {
dashboard: 'Environment metrics',
panel_groups: [
{
group: 'Response metrics (NGINX Ingress VTS)',
priority: 10,
panels: [
{
metrics: [
{
id: 'response_metrics_nginx_ingress_throughput_status_code',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)',
unit: 'req / sec',
},
],
title: 'Throughput',
type: 'area-chart',
weight: 1,
y_label: 'Requests / Sec',
},
],
},
{
group: 'System metrics (Kubernetes)',
priority: 5,
panels: [
{
title: 'Memory Usage (Pod average)',
type: 'area-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
{
id: 'system_metrics_kubernetes_container_memory_average',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
label: 'Pod average',
unit: 'MB',
metric_id: 17,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
appearance: {
line: {
width: 2,
},
},
},
],
},
{
title: 'Core Usage (Total)',
type: 'area-chart',
y_label: 'Total Cores',
weight: 3,
metrics: [
{
id: 'system_metrics_kubernetes_container_cores_total',
query_range:
'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
label: 'Total',
unit: 'cores',
metric_id: 13,
},
],
},
{ {
title: 'memories', title: 'memories',
type: 'area-chart', type: 'area-chart',
...@@ -557,9 +481,45 @@ export const metricsDashboardPayload = { ...@@ -557,9 +481,45 @@ export const metricsDashboardPayload = {
}, },
], ],
}, },
{
group: 'Response metrics (NGINX Ingress VTS)',
priority: 10,
panels: [
{
metrics: [
{
id: 'response_metrics_nginx_ingress_throughput_status_code',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)',
unit: 'req / sec',
},
],
title: 'Throughput',
type: 'area-chart',
weight: 1,
y_label: 'Requests / Sec',
},
], ],
},
],
};
/**
* Mock of response of metrics_dashboard.json
*/
export const metricsDashboardResponse = {
all_dashboards: [],
dashboard: metricsDashboardPayload,
metrics_data: {},
status: 'success',
}; };
export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload);
const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({ const customDashboardsData = new Array(30).fill(null).map((_, idx) => ({
default: false, default: false,
display_name: `Custom Dashboard ${idx}`, display_name: `Custom Dashboard ${idx}`,
......
...@@ -3,7 +3,7 @@ import testAction from 'helpers/vuex_action_helper'; ...@@ -3,7 +3,7 @@ import testAction from 'helpers/vuex_action_helper';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status'; import statusCodes from '~/lib/utils/http_status';
import { backOff } from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import store from '~/monitoring/stores'; import store from '~/monitoring/stores';
...@@ -28,11 +28,10 @@ import { ...@@ -28,11 +28,10 @@ import {
deploymentData, deploymentData,
environmentData, environmentData,
metricsDashboardResponse, metricsDashboardResponse,
metricsDashboardPayload, metricsDashboardViewModel,
dashboardGitResponse, dashboardGitResponse,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash'); jest.mock('~/flash');
const resetStore = str => { const resetStore = str => {
...@@ -44,14 +43,17 @@ const resetStore = str => { ...@@ -44,14 +43,17 @@ const resetStore = str => {
}; };
describe('Monitoring store actions', () => { describe('Monitoring store actions', () => {
const { convertObjectPropsToCamelCase } = commonUtils;
let mock; let mock;
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
// Mock `backOff` function to remove exponential algorithm delay. // Mock `backOff` function to remove exponential algorithm delay.
jest.useFakeTimers(); jest.useFakeTimers();
backOff.mockImplementation(callback => { jest.spyOn(commonUtils, 'backOff').mockImplementation(callback => {
const q = new Promise((resolve, reject) => { const q = new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg)); const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => callback(next, stop); const next = () => callback(next, stop);
...@@ -69,7 +71,7 @@ describe('Monitoring store actions', () => { ...@@ -69,7 +71,7 @@ describe('Monitoring store actions', () => {
resetStore(store); resetStore(store);
mock.reset(); mock.reset();
backOff.mockReset(); commonUtils.backOff.mockReset();
createFlash.mockReset(); createFlash.mockReset();
}); });
...@@ -115,7 +117,6 @@ describe('Monitoring store actions', () => { ...@@ -115,7 +117,6 @@ describe('Monitoring store actions', () => {
afterEach(() => { afterEach(() => {
resetStore(store); resetStore(store);
jest.restoreAllMocks();
}); });
it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => { it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
...@@ -365,6 +366,7 @@ describe('Monitoring store actions', () => { ...@@ -365,6 +366,7 @@ describe('Monitoring store actions', () => {
); );
expect(commit).toHaveBeenCalledWith( expect(commit).toHaveBeenCalledWith(
types.RECEIVE_METRICS_DATA_SUCCESS, types.RECEIVE_METRICS_DATA_SUCCESS,
metricsDashboardResponse.dashboard, metricsDashboardResponse.dashboard,
); );
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params); expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params);
...@@ -443,8 +445,11 @@ describe('Monitoring store actions', () => { ...@@ -443,8 +445,11 @@ describe('Monitoring store actions', () => {
.catch(done.fail); .catch(done.fail);
}); });
it('dispatches fetchPrometheusMetric for each panel query', done => { it('dispatches fetchPrometheusMetric for each panel query', done => {
state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups; state.dashboard.panelGroups = convertObjectPropsToCamelCase(
const [metric] = state.dashboard.panel_groups[0].panels[0].metrics; metricsDashboardResponse.dashboard.panel_groups,
);
const [metric] = state.dashboard.panelGroups[0].panels[0].metrics;
const getters = { const getters = {
metricsWithData: () => [metric.id], metricsWithData: () => [metric.id],
}; };
...@@ -473,16 +478,16 @@ describe('Monitoring store actions', () => { ...@@ -473,16 +478,16 @@ describe('Monitoring store actions', () => {
}); });
it('dispatches fetchPrometheusMetric for each panel query, handles an error', done => { it('dispatches fetchPrometheusMetric for each panel query, handles an error', done => {
state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups; state.dashboard.panelGroups = metricsDashboardViewModel.panelGroups;
const metric = state.dashboard.panel_groups[0].panels[0].metrics[0]; const metric = state.dashboard.panelGroups[0].panels[0].metrics[0];
// Mock having one out of three metrics failing // Mock having one out of four metrics failing
dispatch.mockRejectedValueOnce(new Error('Error fetching this metric')); dispatch.mockRejectedValueOnce(new Error('Error fetching this metric'));
dispatch.mockResolvedValue(); dispatch.mockResolvedValue();
fetchPrometheusMetrics({ state, commit, dispatch }, params) fetchPrometheusMetrics({ state, commit, dispatch }, params)
.then(() => { .then(() => {
expect(dispatch).toHaveBeenCalledTimes(3); expect(dispatch).toHaveBeenCalledTimes(9); // one per metric
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', { expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric, metric,
params, params,
...@@ -508,7 +513,12 @@ describe('Monitoring store actions', () => { ...@@ -508,7 +513,12 @@ describe('Monitoring store actions', () => {
beforeEach(() => { beforeEach(() => {
state = storeState(); state = storeState();
[metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics; [metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics;
[data] = metricsDashboardPayload.panel_groups[0].panels[0].metrics; metric = convertObjectPropsToCamelCase(metric, { deep: true });
data = {
metricId: metric.metricId,
result: [1582065167.353, 5, 1582065599.353],
};
}); });
it('commits result', done => { it('commits result', done => {
...@@ -522,13 +532,13 @@ describe('Monitoring store actions', () => { ...@@ -522,13 +532,13 @@ describe('Monitoring store actions', () => {
{ {
type: types.REQUEST_METRIC_RESULT, type: types.REQUEST_METRIC_RESULT,
payload: { payload: {
metricId: metric.metric_id, metricId: metric.metricId,
}, },
}, },
{ {
type: types.RECEIVE_METRIC_RESULT_SUCCESS, type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: { payload: {
metricId: metric.metric_id, metricId: metric.metricId,
result: data.result, result: data.result,
}, },
}, },
...@@ -556,13 +566,13 @@ describe('Monitoring store actions', () => { ...@@ -556,13 +566,13 @@ describe('Monitoring store actions', () => {
{ {
type: types.REQUEST_METRIC_RESULT, type: types.REQUEST_METRIC_RESULT,
payload: { payload: {
metricId: metric.metric_id, metricId: metric.metricId,
}, },
}, },
{ {
type: types.RECEIVE_METRIC_RESULT_SUCCESS, type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: { payload: {
metricId: metric.metric_id, metricId: metric.metricId,
result: data.result, result: data.result,
}, },
}, },
...@@ -592,13 +602,13 @@ describe('Monitoring store actions', () => { ...@@ -592,13 +602,13 @@ describe('Monitoring store actions', () => {
{ {
type: types.REQUEST_METRIC_RESULT, type: types.REQUEST_METRIC_RESULT,
payload: { payload: {
metricId: metric.metric_id, metricId: metric.metricId,
}, },
}, },
{ {
type: types.RECEIVE_METRIC_RESULT_FAILURE, type: types.RECEIVE_METRIC_RESULT_FAILURE,
payload: { payload: {
metricId: metric.metric_id, metricId: metric.metricId,
error, error,
}, },
}, },
......
...@@ -32,7 +32,7 @@ describe('Monitoring store Getters', () => { ...@@ -32,7 +32,7 @@ describe('Monitoring store Getters', () => {
it('when dashboard has no panel groups, returns empty', () => { it('when dashboard has no panel groups, returns empty', () => {
setupState({ setupState({
dashboard: { dashboard: {
panel_groups: [], panelGroups: [],
}, },
}); });
...@@ -43,10 +43,10 @@ describe('Monitoring store Getters', () => { ...@@ -43,10 +43,10 @@ describe('Monitoring store Getters', () => {
let groups; let groups;
beforeEach(() => { beforeEach(() => {
setupState({ setupState({
dashboard: { panel_groups: [] }, dashboard: { panelGroups: [] },
}); });
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
groups = state.dashboard.panel_groups; groups = state.dashboard.panelGroups;
}); });
it('no loaded metric returns empty', () => { it('no loaded metric returns empty', () => {
...@@ -84,8 +84,8 @@ describe('Monitoring store Getters', () => { ...@@ -84,8 +84,8 @@ describe('Monitoring store Getters', () => {
expect(getMetricStates()).toEqual([metricStates.OK]); expect(getMetricStates()).toEqual([metricStates.OK]);
// Filtered by groups // Filtered by groups
expect(getMetricStates(state.dashboard.panel_groups[0].key)).toEqual([]); expect(getMetricStates(state.dashboard.panelGroups[0].key)).toEqual([metricStates.OK]);
expect(getMetricStates(state.dashboard.panel_groups[1].key)).toEqual([metricStates.OK]); expect(getMetricStates(state.dashboard.panelGroups[1].key)).toEqual([]);
}); });
it('on multiple metrics errors', () => { it('on multiple metrics errors', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
...@@ -94,10 +94,10 @@ describe('Monitoring store Getters', () => { ...@@ -94,10 +94,10 @@ describe('Monitoring store Getters', () => {
metricId: groups[0].panels[0].metrics[0].metricId, metricId: groups[0].panels[0].metrics[0].metricId,
}); });
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[1].panels[0].metrics[0].metricId, metricId: groups[0].panels[0].metrics[0].metricId,
}); });
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[1].panels[1].metrics[0].metricId, metricId: groups[1].panels[0].metrics[0].metricId,
}); });
// Entire dashboard fails // Entire dashboard fails
...@@ -113,18 +113,18 @@ describe('Monitoring store Getters', () => { ...@@ -113,18 +113,18 @@ describe('Monitoring store Getters', () => {
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
// An error in 2 groups // An error in 2 groups
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[0].panels[0].metrics[0].metricId, metricId: groups[0].panels[1].metrics[0].metricId,
}); });
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[1].panels[1].metrics[0].metricId, metricId: groups[1].panels[0].metrics[0].metricId,
}); });
expect(getMetricStates()).toEqual([metricStates.OK, metricStates.UNKNOWN_ERROR]); expect(getMetricStates()).toEqual([metricStates.OK, metricStates.UNKNOWN_ERROR]);
expect(getMetricStates(groups[0].key)).toEqual([metricStates.UNKNOWN_ERROR]); expect(getMetricStates(groups[0].key)).toEqual([
expect(getMetricStates(groups[1].key)).toEqual([
metricStates.OK, metricStates.OK,
metricStates.UNKNOWN_ERROR, metricStates.UNKNOWN_ERROR,
]); ]);
expect(getMetricStates(groups[1].key)).toEqual([metricStates.UNKNOWN_ERROR]);
}); });
}); });
}); });
...@@ -154,7 +154,7 @@ describe('Monitoring store Getters', () => { ...@@ -154,7 +154,7 @@ describe('Monitoring store Getters', () => {
it('when dashboard has no panel groups, returns empty', () => { it('when dashboard has no panel groups, returns empty', () => {
setupState({ setupState({
dashboard: { dashboard: {
panel_groups: [], panelGroups: [],
}, },
}); });
...@@ -164,7 +164,7 @@ describe('Monitoring store Getters', () => { ...@@ -164,7 +164,7 @@ describe('Monitoring store Getters', () => {
describe('when the dashboard is set', () => { describe('when the dashboard is set', () => {
beforeEach(() => { beforeEach(() => {
setupState({ setupState({
dashboard: { panel_groups: [] }, dashboard: { panelGroups: [] },
}); });
}); });
...@@ -204,14 +204,14 @@ describe('Monitoring store Getters', () => { ...@@ -204,14 +204,14 @@ describe('Monitoring store Getters', () => {
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
// First group has no metrics // First group has metrics
expect(metricsWithData(state.dashboard.panel_groups[0].key)).toEqual([]); expect(metricsWithData(state.dashboard.panelGroups[0].key)).toEqual([
// Second group has metrics
expect(metricsWithData(state.dashboard.panel_groups[1].key)).toEqual([
mockedQueryResultPayload.metricId, mockedQueryResultPayload.metricId,
mockedQueryResultPayloadCoresTotal.metricId, mockedQueryResultPayloadCoresTotal.metricId,
]); ]);
// Second group has no metrics
expect(metricsWithData(state.dashboard.panelGroups[1].key)).toEqual([]);
}); });
}); });
}); });
......
...@@ -4,12 +4,8 @@ import mutations from '~/monitoring/stores/mutations'; ...@@ -4,12 +4,8 @@ import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state'; import state from '~/monitoring/stores/state';
import { metricStates } from '~/monitoring/constants'; import { metricStates } from '~/monitoring/constants';
import {
metricsDashboardPayload, import { metricsDashboardPayload, deploymentData, dashboardGitResponse } from '../mock_data';
deploymentData,
metricsDashboardResponse,
dashboardGitResponse,
} from '../mock_data';
describe('Monitoring mutations', () => { describe('Monitoring mutations', () => {
let stateCopy; let stateCopy;
...@@ -17,27 +13,29 @@ describe('Monitoring mutations', () => { ...@@ -17,27 +13,29 @@ describe('Monitoring mutations', () => {
beforeEach(() => { beforeEach(() => {
stateCopy = state(); stateCopy = state();
}); });
describe('RECEIVE_METRICS_DATA_SUCCESS', () => { describe('RECEIVE_METRICS_DATA_SUCCESS', () => {
let payload; let payload;
const getGroups = () => stateCopy.dashboard.panel_groups; const getGroups = () => stateCopy.dashboard.panelGroups;
beforeEach(() => { beforeEach(() => {
stateCopy.dashboard.panel_groups = []; stateCopy.dashboard.panelGroups = [];
payload = metricsDashboardPayload; payload = metricsDashboardPayload;
}); });
it('adds a key to the group', () => { it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
const groups = getGroups(); const groups = getGroups();
expect(groups[0].key).toBe('response-metrics-nginx-ingress-vts-0'); expect(groups[0].key).toBe('system-metrics-kubernetes-0');
expect(groups[1].key).toBe('system-metrics-kubernetes-1'); expect(groups[1].key).toBe('response-metrics-nginx-ingress-vts-1');
}); });
it('normalizes values', () => { it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
const expectedLabel = 'Pod average'; const expectedLabel = 'Pod average';
const { label, query_range } = getGroups()[1].panels[0].metrics[0];
const { label, queryRange } = getGroups()[0].panels[2].metrics[0];
expect(label).toEqual(expectedLabel); expect(label).toEqual(expectedLabel);
expect(query_range.length).toBeGreaterThan(0); expect(queryRange.length).toBeGreaterThan(0);
}); });
it('contains two groups, with panels with a metric each', () => { it('contains two groups, with panels with a metric each', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
...@@ -47,13 +45,14 @@ describe('Monitoring mutations', () => { ...@@ -47,13 +45,14 @@ describe('Monitoring mutations', () => {
expect(groups).toBeDefined(); expect(groups).toBeDefined();
expect(groups).toHaveLength(2); expect(groups).toHaveLength(2);
expect(groups[0].panels).toHaveLength(1); expect(groups[0].panels).toHaveLength(4);
expect(groups[0].panels[0].metrics).toHaveLength(1); expect(groups[0].panels[0].metrics).toHaveLength(1);
expect(groups[0].panels[1].metrics).toHaveLength(1);
expect(groups[0].panels[2].metrics).toHaveLength(1);
expect(groups[0].panels[3].metrics).toHaveLength(5);
expect(groups[1].panels).toHaveLength(3); expect(groups[1].panels).toHaveLength(1);
expect(groups[1].panels[0].metrics).toHaveLength(1); expect(groups[1].panels[0].metrics).toHaveLength(1);
expect(groups[1].panels[1].metrics).toHaveLength(1);
expect(groups[1].panels[2].metrics).toHaveLength(5);
}); });
it('assigns metrics a metric id', () => { it('assigns metrics a metric id', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
...@@ -61,10 +60,10 @@ describe('Monitoring mutations', () => { ...@@ -61,10 +60,10 @@ describe('Monitoring mutations', () => {
const groups = getGroups(); const groups = getGroups();
expect(groups[0].panels[0].metrics[0].metricId).toEqual( expect(groups[0].panels[0].metrics[0].metricId).toEqual(
'1_response_metrics_nginx_ingress_throughput_status_code', '12_system_metrics_kubernetes_container_memory_total',
); );
expect(groups[1].panels[0].metrics[0].metricId).toEqual( expect(groups[1].panels[0].metrics[0].metricId).toEqual(
'17_system_metrics_kubernetes_container_memory_average', '1_response_metrics_nginx_ingress_throughput_status_code',
); );
}); });
}); });
...@@ -130,8 +129,8 @@ describe('Monitoring mutations', () => { ...@@ -130,8 +129,8 @@ describe('Monitoring mutations', () => {
values: [[0, 1], [1, 1], [1, 3]], values: [[0, 1], [1, 1], [1, 3]],
}, },
]; ];
const { dashboard } = metricsDashboardResponse; const dashboard = metricsDashboardPayload;
const getMetric = () => stateCopy.dashboard.panel_groups[0].panels[0].metrics[0]; const getMetric = () => stateCopy.dashboard.panelGroups[0].panels[0].metrics[0];
describe('REQUEST_METRIC_RESULT', () => { describe('REQUEST_METRIC_RESULT', () => {
beforeEach(() => { beforeEach(() => {
......
import { import {
normalizeMetric,
uniqMetricsId, uniqMetricsId,
parseEnvironmentsResponse, parseEnvironmentsResponse,
removeLeadingSlash, removeLeadingSlash,
mapToDashboardViewModel,
} from '~/monitoring/stores/utils'; } from '~/monitoring/stores/utils';
const projectPath = 'gitlab-org/gitlab-test'; const projectPath = 'gitlab-org/gitlab-test';
describe('normalizeMetric', () => { describe('mapToDashboardViewModel', () => {
[ it('maps an empty dashboard', () => {
{ args: [], expected: 'undefined_undefined' }, expect(mapToDashboardViewModel({})).toEqual({
{ args: [undefined], expected: 'undefined_undefined' }, dashboard: '',
{ args: [{ id: 'something' }], expected: 'undefined_something' }, panelGroups: [],
{ args: [{ id: 45 }], expected: 'undefined_45' }, });
{ args: [{ metric_id: 5 }], expected: '5_undefined' }, });
{ args: [{ metric_id: 'something' }], expected: 'something_undefined' },
{ it('maps a simple dashboard', () => {
args: [{ metric_id: 5, id: 'system_metrics_kubernetes_container_memory_total' }], const response = {
expected: '5_system_metrics_kubernetes_container_memory_total', dashboard: 'Dashboard Name',
}, panel_groups: [
].forEach(({ args, expected }) => { {
it(`normalizes metric to "${expected}" with args=${JSON.stringify(args)}`, () => { group: 'Group 1',
expect(normalizeMetric(...args)).toEqual({ metric_id: expected, metricId: expected }); panels: [
{
title: 'Title A',
type: 'chart-type',
y_label: 'Y Label A',
metrics: [],
},
],
},
],
};
expect(mapToDashboardViewModel(response)).toEqual({
dashboard: 'Dashboard Name',
panelGroups: [
{
group: 'Group 1',
key: 'group-1-0',
panels: [
{
title: 'Title A',
type: 'chart-type',
y_label: 'Y Label A',
metrics: [],
},
],
},
],
});
});
describe('panel groups mapping', () => {
it('key', () => {
const response = {
dashboard: 'Dashboard Name',
panel_groups: [
{
group: 'Group A',
},
{
group: 'Group B',
},
{
group: '',
unsupported_property: 'This should be removed',
},
],
};
expect(mapToDashboardViewModel(response).panelGroups).toEqual([
{
group: 'Group A',
key: 'group-a-0',
panels: [],
},
{
group: 'Group B',
key: 'group-b-1',
panels: [],
},
{
group: '',
key: 'default-2',
panels: [],
},
]);
});
});
describe('metrics mapping', () => {
const defaultLabel = 'Panel Label';
const dashboardWithMetric = (metric, label = defaultLabel) => ({
panel_groups: [
{
panels: [
{
y_label: label,
metrics: [metric],
},
],
},
],
});
const getMappedMetric = dashboard => {
return mapToDashboardViewModel(dashboard).panelGroups[0].panels[0].metrics[0];
};
it('creates a metric', () => {
const dashboard = dashboardWithMetric({});
expect(getMappedMetric(dashboard)).toEqual({
label: expect.any(String),
metricId: expect.any(String),
metric_id: expect.any(String),
});
});
it('creates a metric with a correct ids', () => {
const dashboard = dashboardWithMetric({
id: 'http_responses',
metric_id: 1,
});
expect(getMappedMetric(dashboard)).toMatchObject({
metricId: '1_http_responses',
metric_id: '1_http_responses',
});
});
it('creates a metric with a default label', () => {
const dashboard = dashboardWithMetric({});
expect(getMappedMetric(dashboard)).toMatchObject({
label: defaultLabel,
});
});
it('creates a metric with an endpoint and query', () => {
const dashboard = dashboardWithMetric({
prometheus_endpoint_path: 'http://test',
query_range: 'http_responses',
});
expect(getMappedMetric(dashboard)).toMatchObject({
prometheusEndpointPath: 'http://test',
queryRange: 'http_responses',
});
});
it('creates a metric with an ad-hoc property', () => {
// This behavior is deprecated and should be removed
// https://gitlab.com/gitlab-org/gitlab/issues/207198
const dashboard = dashboardWithMetric({
x_label: 'Another label',
unkown_option: 'unkown_data',
});
expect(getMappedMetric(dashboard)).toMatchObject({
x_label: 'Another label',
unkown_option: 'unkown_data',
});
}); });
}); });
}); });
......
...@@ -112,7 +112,7 @@ describe('Dashboard', () => { ...@@ -112,7 +112,7 @@ describe('Dashboard', () => {
setupComponentStore(component); setupComponentStore(component);
return Vue.nextTick().then(() => { return Vue.nextTick().then(() => {
[, promPanel] = component.$el.querySelectorAll('.prometheus-panel'); [promPanel] = component.$el.querySelectorAll('.prometheus-panel');
promGroup = promPanel.querySelector('.prometheus-graph-group'); promGroup = promPanel.querySelector('.prometheus-graph-group');
panelToggle = promPanel.querySelector('.js-graph-group-toggle'); panelToggle = promPanel.querySelector('.js-graph-group-toggle');
chart = promGroup.querySelector('.position-relative svg'); chart = promGroup.querySelector('.position-relative svg');
......
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