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 {
return this.customMetricsAvailable && this.customMetricsPath.length;
},
...mapState('monitoringDashboard', [
'groups',
'dashboard',
'emptyState',
'showEmptyState',
'environments',
......@@ -238,6 +238,7 @@ export default {
'setGettingStartedEmptyState',
'setEndpoints',
'setDashboardEnabled',
'setPanelGroupMetrics',
]),
chartsWithData(charts) {
if (!this.useDashboardEndpoint) {
......@@ -274,10 +275,17 @@ export default {
this.$toast.show(__('Link copied'));
},
// TODO: END
removeGraph(metrics, graphIndex) {
// At present graphs will not be removed, they should removed using the vuex store
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
metrics.splice(graphIndex, 1);
updateMetrics(key, metrics) {
this.setPanelGroupMetrics({
metrics,
key,
});
},
removeMetric(key, metrics, graphIndex) {
this.setPanelGroupMetrics({
metrics: metrics.filter((v, i) => i !== graphIndex),
key,
});
},
showInvalidDateError() {
createFlash(s__('Metrics|Link contains an invalid time window.'));
......@@ -447,7 +455,7 @@ export default {
<div v-if="!showEmptyState">
<graph-group
v-for="(groupData, index) in groups"
v-for="(groupData, index) in dashboard.panel_groups"
:key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
......@@ -455,10 +463,11 @@ export default {
>
<template v-if="additionalPanelTypesEnabled">
<vue-draggable
:list="groupData.metrics"
:value="groupData.metrics"
group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels"
@input="updateMetrics(groupData.key, $event)"
>
<div
v-for="(graphData, graphIndex) in groupData.metrics"
......@@ -470,7 +479,7 @@ export default {
<div
v-if="isRearrangingPanels"
class="draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
@click="removeGraph(groupData.metrics, graphIndex)"
@click="removeMetric(groupData.key, groupData.metrics, graphIndex)"
>
<a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')"
><icon name="close"
......
......@@ -35,9 +35,9 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['groups', 'metricsWithData']),
...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
charts() {
const groupWithMetrics = this.groups.find(group =>
const groupWithMetrics = this.dashboard.panel_groups.find(group =>
group.metrics.find(chart => this.chartHasData(chart)),
) || { metrics: [] };
......
......@@ -166,7 +166,7 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
commit(types.REQUEST_METRICS_DATA);
const promises = [];
state.groups.forEach(group => {
state.dashboard.panel_groups.forEach(group => {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
promises.push(dispatch('fetchPrometheusMetric', { metric, params }));
......@@ -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
export default () => {};
......@@ -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_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
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 { slugify } from '~/lib/utils/text_utility';
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);
......@@ -10,10 +11,12 @@ export default {
state.showEmptyState = true;
},
[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;
// each panel has metric information that needs to be normalized
panels = panels.map(panel => ({
...panel,
metrics: normalizePanel(panel),
......@@ -32,11 +35,12 @@ export default {
return {
...group,
panels,
metrics: normalizeMetrics(sortMetrics(metrics)),
key,
metrics: normalizeMetrics(metrics),
};
});
if (!state.groups.length) {
if (!state.dashboard.panel_groups.length) {
state.emptyState = 'noData';
} else {
state.showEmptyState = false;
......@@ -65,7 +69,7 @@ export default {
state.showEmptyState = false;
state.groups.forEach(group => {
state.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => {
metric.queries.forEach(query => {
if (query.metric_id === metricId) {
......@@ -105,4 +109,8 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, 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 () => ({
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
groups: [],
dashboard: {
panel_groups: [],
},
deploymentData: [],
environments: [],
metricsWithData: [],
......
......@@ -82,12 +82,6 @@ export const normalizeMetric = (metric = {}) =>
'id',
);
export const sortMetrics = metrics =>
_.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
export const normalizeQueryResult = timeSeries => {
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', () => {
describe('metrics are available', () => {
beforeEach(() => {
store.state.monitoringDashboard.groups = groups;
store.state.monitoringDashboard.groups[0].metrics = metricsData;
store.state.monitoringDashboard.dashboard.panel_groups = groups;
store.state.monitoringDashboard.dashboard.panel_groups[0].metrics = metricsData;
store.state.monitoringDashboard.metricsWithData = metricsWithData;
mountComponent();
......
......@@ -81,7 +81,9 @@ export const metricsData = [
export const initialState = {
monitoringDashboard: {},
groups: [],
dashboard: {
panel_groups: [],
},
metricsWithData: [],
useDashboardEndpoint: true,
};
......@@ -23,7 +23,7 @@ describe('Time series component', () => {
store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
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) =>
shallowMount(TimeSeries, {
......
......@@ -442,6 +442,28 @@ describe('Dashboard', () => {
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 => {
expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
......@@ -449,8 +471,6 @@ describe('Dashboard', () => {
findFirstDraggableRemoveButton().trigger('click');
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);
done();
});
......@@ -686,7 +706,9 @@ describe('Dashboard', () => {
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
MonitoringMock.data,
);
[mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
[
mockGraphData,
] = component.$store.state.monitoringDashboard.dashboard.panel_groups[0].metrics;
});
describe('csvText', () => {
......
......@@ -291,9 +291,9 @@ describe('Monitoring store actions', () => {
it('dispatches fetchPrometheusMetric for each panel query', done => {
const params = {};
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)
.then(() => {
......
......@@ -20,16 +20,26 @@ describe('Monitoring mutations', () => {
let groups;
beforeEach(() => {
stateCopy.groups = [];
stateCopy.dashboard.panel_groups = [];
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', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
const expectedTimestamp = '2017-05-25T08:22:34.925Z';
const expectedValue = 0.0010794445585559514;
const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0];
const expectedValue = 8.0390625;
const [
timestamp,
value,
] = stateCopy.dashboard.panel_groups[0].metrics[0].queries[0].result[0].values[0];
expect(timestamp).toEqual(expectedTimestamp);
expect(value).toEqual(expectedValue);
......@@ -38,25 +48,25 @@ describe('Monitoring mutations', () => {
it('contains two groups that contains, one of which has two queries sorted by priority', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
expect(stateCopy.groups).toBeDefined();
expect(stateCopy.groups.length).toEqual(2);
expect(stateCopy.groups[0].metrics.length).toEqual(2);
expect(stateCopy.dashboard.panel_groups).toBeDefined();
expect(stateCopy.dashboard.panel_groups.length).toEqual(2);
expect(stateCopy.dashboard.panel_groups[0].metrics.length).toEqual(2);
});
it('assigns queries a metric id', () => {
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', () => {
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', () => {
stateCopy.groups = [];
stateCopy.dashboard.panel_groups = [];
const noId = groups.map(group => ({
...group,
...{
......@@ -70,7 +80,7 @@ describe('Monitoring mutations', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
stateCopy.groups.forEach(group => {
stateCopy.dashboard.panel_groups.forEach(group => {
group.metrics.forEach(metric => {
expect(metric.queries.every(query => query.metricId === null)).toBe(true);
});
......@@ -87,13 +97,13 @@ describe('Monitoring mutations', () => {
it('aliases group panels to metrics for backwards compatibility', () => {
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', () => {
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