Commit fa85cc33 authored by Mike Greiling's avatar Mike Greiling

Merge branch '34519-move-planels-in-dashboard-save-to-the-vuex-store' into 'master'

Metrics: Save changes in the panels in dashboard to vuex store

See merge request gitlab-org/gitlab!18862
parents d35854b9 6156edf2
...@@ -174,7 +174,7 @@ export default { ...@@ -174,7 +174,7 @@ export default {
return this.customMetricsAvailable && this.customMetricsPath.length; return this.customMetricsAvailable && this.customMetricsPath.length;
}, },
...mapState('monitoringDashboard', [ ...mapState('monitoringDashboard', [
'groups', 'dashboard',
'emptyState', 'emptyState',
'showEmptyState', 'showEmptyState',
'environments', 'environments',
...@@ -238,6 +238,7 @@ export default { ...@@ -238,6 +238,7 @@ export default {
'setGettingStartedEmptyState', 'setGettingStartedEmptyState',
'setEndpoints', 'setEndpoints',
'setDashboardEnabled', 'setDashboardEnabled',
'setPanelGroupMetrics',
]), ]),
chartsWithData(charts) { chartsWithData(charts) {
if (!this.useDashboardEndpoint) { if (!this.useDashboardEndpoint) {
...@@ -274,10 +275,17 @@ export default { ...@@ -274,10 +275,17 @@ export default {
this.$toast.show(__('Link copied')); this.$toast.show(__('Link copied'));
}, },
// TODO: END // TODO: END
removeGraph(metrics, graphIndex) { updateMetrics(key, metrics) {
// At present graphs will not be removed, they should removed using the vuex store this.setPanelGroupMetrics({
// See https://gitlab.com/gitlab-org/gitlab/issues/27835 metrics,
metrics.splice(graphIndex, 1); key,
});
},
removeMetric(key, metrics, graphIndex) {
this.setPanelGroupMetrics({
metrics: metrics.filter((v, i) => i !== graphIndex),
key,
});
}, },
showInvalidDateError() { showInvalidDateError() {
createFlash(s__('Metrics|Link contains an invalid time window.')); createFlash(s__('Metrics|Link contains an invalid time window.'));
...@@ -447,7 +455,7 @@ export default { ...@@ -447,7 +455,7 @@ export default {
<div v-if="!showEmptyState"> <div v-if="!showEmptyState">
<graph-group <graph-group
v-for="(groupData, index) in groups" v-for="(groupData, index) in dashboard.panel_groups"
:key="`${groupData.group}.${groupData.priority}`" :key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group" :name="groupData.group"
:show-panels="showPanels" :show-panels="showPanels"
...@@ -455,10 +463,11 @@ export default { ...@@ -455,10 +463,11 @@ export default {
> >
<template v-if="additionalPanelTypesEnabled"> <template v-if="additionalPanelTypesEnabled">
<vue-draggable <vue-draggable
:list="groupData.metrics" :value="groupData.metrics"
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)"
> >
<div <div
v-for="(graphData, graphIndex) in groupData.metrics" v-for="(graphData, graphIndex) in groupData.metrics"
...@@ -470,7 +479,7 @@ export default { ...@@ -470,7 +479,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="removeMetric(groupData.key, groupData.metrics, 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"
......
...@@ -35,9 +35,9 @@ export default { ...@@ -35,9 +35,9 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('monitoringDashboard', ['groups', 'metricsWithData']), ...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() { charts() {
const groupWithMetrics = this.groups.find(group => const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)), group.metrics.find(chart => this.chartHasData(chart)),
) || { metrics: [] }; ) || { metrics: [] };
......
...@@ -166,7 +166,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => { ...@@ -166,7 +166,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
commit(types.REQUEST_METRICS_DATA); commit(types.REQUEST_METRICS_DATA);
const promises = []; const promises = [];
state.groups.forEach(group => { state.dashboard.panel_groups.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 }));
...@@ -221,5 +221,15 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => { ...@@ -221,5 +221,15 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => {
}); });
}; };
/**
* Set a new array of metrics to a panel group
* @param {*} data An object containing
* - `key` with a unique panel key
* - `metrics` with the metrics array
*/
export const setPanelGroupMetrics = ({ commit }, data) => {
commit(types.SET_PANEL_GROUP_METRICS, data);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -16,3 +16,4 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS'; ...@@ -16,3 +16,4 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE'; export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE'; export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER'; export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
import Vue from 'vue'; import Vue from 'vue';
import { slugify } from '~/lib/utils/text_utility';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { normalizeMetrics, sortMetrics, normalizeMetric, normalizeQueryResult } from './utils'; import { normalizeMetrics, normalizeMetric, normalizeQueryResult } from './utils';
const normalizePanel = panel => panel.metrics.map(normalizeMetric); const normalizePanel = panel => panel.metrics.map(normalizeMetric);
...@@ -10,10 +11,12 @@ export default { ...@@ -10,10 +11,12 @@ export default {
state.showEmptyState = true; state.showEmptyState = true;
}, },
[types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) { [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
state.groups = groupData.map(group => { state.dashboard.panel_groups = groupData.map((group, i) => {
const key = `${slugify(group.group || 'default')}-${i}`;
let { metrics = [], panels = [] } = group; let { metrics = [], 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: normalizePanel(panel),
...@@ -32,11 +35,12 @@ export default { ...@@ -32,11 +35,12 @@ export default {
return { return {
...group, ...group,
panels, panels,
metrics: normalizeMetrics(sortMetrics(metrics)), key,
metrics: normalizeMetrics(metrics),
}; };
}); });
if (!state.groups.length) { if (!state.dashboard.panel_groups.length) {
state.emptyState = 'noData'; state.emptyState = 'noData';
} else { } else {
state.showEmptyState = false; state.showEmptyState = false;
...@@ -65,7 +69,7 @@ export default { ...@@ -65,7 +69,7 @@ export default {
state.showEmptyState = false; state.showEmptyState = false;
state.groups.forEach(group => { state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => { group.metrics.forEach(metric => {
metric.queries.forEach(query => { metric.queries.forEach(query => {
if (query.metric_id === metricId) { if (query.metric_id === metricId) {
...@@ -105,4 +109,8 @@ export default { ...@@ -105,4 +109,8 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, enabled) { [types.SET_SHOW_ERROR_BANNER](state, enabled) {
state.showErrorBanner = enabled; state.showErrorBanner = enabled;
}, },
[types.SET_PANEL_GROUP_METRICS](state, payload) {
const panelGroup = state.dashboard.panel_groups.find(pg => payload.key === pg.key);
panelGroup.metrics = payload.metrics;
},
}; };
...@@ -12,7 +12,9 @@ export default () => ({ ...@@ -12,7 +12,9 @@ export default () => ({
emptyState: 'gettingStarted', emptyState: 'gettingStarted',
showEmptyState: true, showEmptyState: true,
showErrorBanner: true, showErrorBanner: true,
groups: [], dashboard: {
panel_groups: [],
},
deploymentData: [], deploymentData: [],
environments: [], environments: [],
metricsWithData: [], metricsWithData: [],
......
...@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) => ...@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) =>
'id', 'id',
); );
export const sortMetrics = metrics =>
_.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
export const normalizeQueryResult = timeSeries => { export const normalizeQueryResult = timeSeries => {
let normalizedResult = {}; let normalizedResult = {};
......
---
title: Save dashboard changes by the user into the vuex store
merge_request: 18862
author:
type: changed
...@@ -61,8 +61,8 @@ describe('Embed', () => { ...@@ -61,8 +61,8 @@ describe('Embed', () => {
describe('metrics are available', () => { describe('metrics are available', () => {
beforeEach(() => { beforeEach(() => {
store.state.monitoringDashboard.groups = groups; store.state.monitoringDashboard.dashboard.panel_groups = groups;
store.state.monitoringDashboard.groups[0].metrics = metricsData; store.state.monitoringDashboard.dashboard.panel_groups[0].metrics = metricsData;
store.state.monitoringDashboard.metricsWithData = metricsWithData; store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent(); mountComponent();
......
...@@ -81,7 +81,9 @@ export const metricsData = [ ...@@ -81,7 +81,9 @@ export const metricsData = [
export const initialState = { export const initialState = {
monitoringDashboard: {}, monitoringDashboard: {},
groups: [], dashboard: {
panel_groups: [],
},
metricsWithData: [], metricsWithData: [],
useDashboardEndpoint: true, useDashboardEndpoint: true,
}; };
...@@ -23,7 +23,7 @@ describe('Time series component', () => { ...@@ -23,7 +23,7 @@ describe('Time series component', () => {
store = createStore(); store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data); store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
[mockGraphData] = store.state.monitoringDashboard.groups[0].metrics; [, mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
makeTimeSeriesChart = (graphData, type) => makeTimeSeriesChart = (graphData, type) =>
shallowMount(TimeSeries, { shallowMount(TimeSeries, {
......
...@@ -442,6 +442,28 @@ describe('Dashboard', () => { ...@@ -442,6 +442,28 @@ describe('Dashboard', () => {
expect(findEnabledDraggables()).toEqual(findDraggables()); expect(findEnabledDraggables()).toEqual(findDraggables());
}); });
it('metrics can be swapped', done => {
const firstDraggable = findDraggables().at(0);
const mockMetrics = [...metricsGroupsAPIResponse.data[0].metrics];
const value = () => firstDraggable.props('value');
expect(value().length).toBe(mockMetrics.length);
value().forEach((metric, i) => {
expect(metric.title).toBe(mockMetrics[i].title);
});
// swap two elements and `input` them
[mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]];
firstDraggable.vm.$emit('input', mockMetrics);
firstDraggable.vm.$nextTick(() => {
value().forEach((metric, i) => {
expect(metric.title).toBe(mockMetrics[i].title);
});
done();
});
});
it('shows a remove button, which removes a panel', done => { it('shows a remove button, which removes a panel', done => {
expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false); expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
...@@ -449,8 +471,6 @@ describe('Dashboard', () => { ...@@ -449,8 +471,6 @@ describe('Dashboard', () => {
findFirstDraggableRemoveButton().trigger('click'); findFirstDraggableRemoveButton().trigger('click');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
// At present graphs will not be removed in backend
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1); expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1);
done(); done();
}); });
...@@ -686,7 +706,9 @@ describe('Dashboard', () => { ...@@ -686,7 +706,9 @@ describe('Dashboard', () => {
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
MonitoringMock.data, MonitoringMock.data,
); );
[mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics; [
mockGraphData,
] = component.$store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
}); });
describe('csvText', () => { describe('csvText', () => {
......
...@@ -291,9 +291,9 @@ describe('Monitoring store actions', () => { ...@@ -291,9 +291,9 @@ describe('Monitoring store actions', () => {
it('dispatches fetchPrometheusMetric for each panel query', done => { it('dispatches fetchPrometheusMetric for each panel query', done => {
const params = {}; const params = {};
const state = storeState(); const state = storeState();
state.groups = metricsDashboardResponse.dashboard.panel_groups; state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups;
const metric = state.groups[0].panels[0].metrics[0]; const metric = state.dashboard.panel_groups[0].panels[0].metrics[0];
fetchPrometheusMetrics({ state, commit, dispatch }, params) fetchPrometheusMetrics({ state, commit, dispatch }, params)
.then(() => { .then(() => {
......
...@@ -20,16 +20,26 @@ describe('Monitoring mutations', () => { ...@@ -20,16 +20,26 @@ describe('Monitoring mutations', () => {
let groups; let groups;
beforeEach(() => { beforeEach(() => {
stateCopy.groups = []; stateCopy.dashboard.panel_groups = [];
groups = metricsGroupsAPIResponse.data; groups = metricsGroupsAPIResponse.data;
}); });
it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.dashboard.panel_groups[0].key).toBe('kubernetes-0');
expect(stateCopy.dashboard.panel_groups[1].key).toBe('nginx-1');
});
it('normalizes values', () => { it('normalizes values', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedTimestamp = '2017-05-25T08:22:34.925Z'; const expectedTimestamp = '2017-05-25T08:22:34.925Z';
const expectedValue = 0.0010794445585559514; const expectedValue = 8.0390625;
const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0]; const [
timestamp,
value,
] = stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].result[0].values[0];
expect(timestamp).toEqual(expectedTimestamp); expect(timestamp).toEqual(expectedTimestamp);
expect(value).toEqual(expectedValue); expect(value).toEqual(expectedValue);
...@@ -38,25 +48,25 @@ describe('Monitoring mutations', () => { ...@@ -38,25 +48,25 @@ describe('Monitoring mutations', () => {
it('contains two groups that contains, one of which has two queries sorted by priority', () => { it('contains two groups that contains, one of which has two queries sorted by priority', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups).toBeDefined(); expect(stateCopy.dashboard.panel_groups).toBeDefined();
expect(stateCopy.groups.length).toEqual(2); expect(stateCopy.dashboard.panel_groups.length).toEqual(2);
expect(stateCopy.groups[0].metrics.length).toEqual(2); expect(stateCopy.dashboard.panel_groups[0].metrics.length).toEqual(2);
}); });
it('assigns queries a metric id', () => { it('assigns queries a metric id', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100'); expect(stateCopy.dashboard.panel_groups[1].metrics[0].queries[0].metricId).toEqual('100');
}); });
it('removes the data if all the values from a query are not defined', () => { it('removes the data if all the values from a query are not defined', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0); expect(stateCopy.dashboard.panel_groups[1].metrics[0].queries[0].result.length).toEqual(0);
}); });
it('assigns metric id of null if metric has no id', () => { it('assigns metric id of null if metric has no id', () => {
stateCopy.groups = []; stateCopy.dashboard.panel_groups = [];
const noId = groups.map(group => ({ const noId = groups.map(group => ({
...group, ...group,
...{ ...{
...@@ -70,7 +80,7 @@ describe('Monitoring mutations', () => { ...@@ -70,7 +80,7 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
stateCopy.groups.forEach(group => { stateCopy.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => { group.metrics.forEach(metric => {
expect(metric.queries.every(query => query.metricId === null)).toBe(true); expect(metric.queries.every(query => query.metricId === null)).toBe(true);
}); });
...@@ -87,13 +97,13 @@ describe('Monitoring mutations', () => { ...@@ -87,13 +97,13 @@ describe('Monitoring mutations', () => {
it('aliases group panels to metrics for backwards compatibility', () => { it('aliases group panels to metrics for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
expect(stateCopy.groups[0].metrics[0]).toBeDefined(); expect(stateCopy.dashboard.panel_groups[0].metrics[0]).toBeDefined();
}); });
it('aliases panel metrics to queries for backwards compatibility', () => { it('aliases panel metrics to queries for backwards compatibility', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
expect(stateCopy.groups[0].metrics[0].queries).toBeDefined(); expect(stateCopy.dashboard.panel_groups[0].metrics[0].queries).toBeDefined();
}); });
}); });
}); });
......
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