Commit d0e486fb authored by Miguel Rincon's avatar Miguel Rincon Committed by Fatih Acet

Enable metrics dashboards to be rearranged

- Add feature to have panels drag and drop using vuedraggable
- Enable draggable panels have a different appearance
- Add property to enable feature, disabled by default
- Add vuedraggable as a dependency
parent 88972e49
<script>
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import VueDraggable from 'vuedraggable';
import {
GlButton,
GlDropdown,
......@@ -8,8 +11,6 @@ import {
GlModalDirective,
GlTooltipDirective,
} from '@gitlab/ui';
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import { __, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
......@@ -26,6 +27,7 @@ let sidebarMutationObserver;
export default {
components: {
VueDraggable,
MonitorTimeSeriesChart,
MonitorSingleStatChart,
PanelType,
......@@ -151,6 +153,11 @@ export default {
required: false,
default: false,
},
rearrangePanelsAvailable: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -160,6 +167,7 @@ export default {
selectedTimeWindowKey: '',
formIsValid: null,
timeWindows: {},
isRearrangingPanels: false,
};
},
computed: {
......@@ -183,6 +191,9 @@ export default {
selectedDashboardText() {
return this.currentDashboard || this.firstDashboard.display_name;
},
showRearrangePanelsBtn() {
return !this.showEmptyState && this.rearrangePanelsAvailable;
},
addingMetricsAvailable() {
return IS_EE && this.canAddMetrics && !this.showEmptyState;
},
......@@ -274,6 +285,11 @@ 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);
},
generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path;
const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
......@@ -287,6 +303,9 @@ export default {
this.elWidth = this.$el.clientWidth;
}, sidebarAnimationDuration);
},
toggleRearrangingPanels() {
this.isRearrangingPanels = !this.isRearrangingPanels;
},
setFormValidity(isValid) {
this.formIsValid = isValid;
},
......@@ -389,15 +408,27 @@ export default {
</template>
<gl-form-group
v-if="addingMetricsAvailable || externalDashboardUrl.length"
v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length"
label-for="prometheus-graphs-dropdown-buttons"
class="dropdown-buttons col-lg d-lg-flex align-items-end"
>
<div id="prometheus-graphs-dropdown-buttons">
<gl-button
v-if="showRearrangePanelsBtn"
:pressed="isRearrangingPanels"
new-style
variant="default"
class="mr-2 mt-1 js-rearrange-button"
@click="toggleRearrangingPanels"
>
{{ __('Arrange charts') }}
</gl-button>
<gl-button
v-if="addingMetricsAvailable"
v-gl-modal="$options.addMetric.modalId"
class="mr-2 mt-1 js-add-metric-button text-success border-success"
new-style
variant="outline-success"
class="mr-2 mt-1 js-add-metric-button"
>
{{ $options.addMetric.title }}
</gl-button>
......@@ -451,17 +482,42 @@ export default {
:collapse-group="groupHasData(groupData)"
>
<template v-if="additionalPanelTypesEnabled">
<panel-type
v-for="(graphData, graphIndex) in groupData.metrics"
:key="`panel-type-${graphIndex}`"
class="col-12 col-lg-6 pb-3"
:clipboard-text="generateLink(groupData.group, graphData.title, graphData.y_label)"
:graph-data="graphData"
:dashboard-width="elWidth"
:alerts-endpoint="alertsEndpoint"
:prometheus-alerts-available="prometheusAlertsAvailable"
:index="`${index}-${graphIndex}`"
/>
<vue-draggable
:list="groupData.metrics"
group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels"
>
<div
v-for="(graphData, graphIndex) in groupData.metrics"
:key="`panel-type-${graphIndex}`"
class="col-12 col-lg-6 px-2 mb-2 draggable"
:class="{ 'draggable-enabled': isRearrangingPanels }"
>
<div class="position-relative draggable-panel js-draggable-panel">
<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)"
>
<a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')"
><icon name="close"
/></a>
</div>
<panel-type
:clipboard-text="
generateLink(groupData.group, graphData.title, graphData.y_label)
"
:graph-data="graphData"
:dashboard-width="elWidth"
:alerts-endpoint="alertsEndpoint"
:prometheus-alerts-available="prometheusAlertsAvailable"
:index="`${index}-${graphIndex}`"
/>
</div>
</div>
</vue-draggable>
</template>
<template v-else>
<monitor-time-series-chart
......
......@@ -52,7 +52,7 @@ export default {
<div
v-if="collapseGroup"
v-show="collapseGroup && showGroup"
class="card-body prometheus-graph-group"
class="card-body prometheus-graph-group p-0"
>
<slot></slot>
</div>
......
......@@ -15,6 +15,37 @@
}
}
.draggable {
&.draggable-enabled {
.draggable-panel {
border: $gray-200 1px solid;
border-radius: $border-radius-default;
margin: -1px;
cursor: grab;
}
.prometheus-graph {
// Make dragging easier by disabling use of chart
pointer-events: none;
}
}
&.sortable-chosen .draggable-panel {
background: $white-light;
box-shadow: 0 0 4px $gray-500;
}
.draggable-remove {
z-index: 1;
.draggable-remove-link {
cursor: pointer;
color: $gray-600;
background-color: $white-light;
}
}
}
.prometheus-panel {
margin-top: 20px;
}
......@@ -22,11 +53,11 @@
.prometheus-graph-group {
display: flex;
flex-wrap: wrap;
padding: $gl-padding / 2;
margin-top: $gl-padding-8;
}
.prometheus-graph {
padding: $gl-padding / 2;
padding: $gl-padding-8;
}
.prometheus-graph-embed {
......
---
title: Add property to enable metrics dashboards to be rearranged
merge_request: 16605
author:
type: changed
......@@ -1889,6 +1889,9 @@ msgstr ""
msgid "Are you sure? This will invalidate your registered applications and U2F devices."
msgstr ""
msgid "Arrange charts"
msgstr ""
msgid "Artifact ID"
msgstr ""
......
import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlToast } from '@gitlab/ui';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
......@@ -51,11 +52,6 @@ describe('Dashboard', () => {
<div class="layout-page"></div>
`);
window.gon = {
...window.gon,
ee: false,
};
store = createStore();
mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard);
......@@ -378,7 +374,101 @@ describe('Dashboard', () => {
});
});
// https://gitlab.com/gitlab-org/gitlab-foss/issues/66922
describe('drag and drop function', () => {
let wrapper;
let expectedPanelCount; // also called metrics, naming to be improved: https://gitlab.com/gitlab-org/gitlab/issues/31565
const findDraggables = () => wrapper.findAll(VueDraggable);
const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled'));
const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel');
const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
expectedPanelCount = metricsGroupsAPIResponse.data.reduce(
(acc, d) => d.metrics.length + acc,
0,
);
store.dispatch('monitoringDashboard/setFeatureFlags', { additionalPanelTypesEnabled: true });
wrapper = shallowMount(DashboardComponent, {
localVue,
sync: false,
propsData: { ...propsData, hasMetrics: true },
store,
});
// not using $nextTicket becuase we must wait for the dashboard
// to be populated with the mock data results.
setTimeout(done);
});
it('wraps vuedraggable', () => {
expect(findDraggablePanels().exists()).toBe(true);
expect(findDraggablePanels().length).toEqual(expectedPanelCount);
});
it('is disabled by default', () => {
expect(findRearrangeButton().exists()).toBe(false);
expect(findEnabledDraggables().length).toBe(0);
});
describe('when rearrange is enabled', () => {
beforeEach(done => {
wrapper.setProps({ rearrangePanelsAvailable: true });
wrapper.vm.$nextTick(done);
});
it('displays rearrange button', () => {
expect(findRearrangeButton().exists()).toBe(true);
});
describe('when rearrange button is clicked', () => {
const findFirstDraggableRemoveButton = () =>
findDraggablePanels()
.at(0)
.find('.js-draggable-remove');
beforeEach(done => {
findRearrangeButton().vm.$emit('click');
wrapper.vm.$nextTick(done);
});
it('it enables draggables', () => {
expect(findRearrangeButton().attributes('pressed')).toBeTruthy();
expect(findEnabledDraggables()).toEqual(findDraggables());
});
it('shows a remove button, which removes a panel', done => {
expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
expect(findDraggablePanels().length).toEqual(expectedPanelCount);
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();
});
});
it('it disables draggables when clicked again', done => {
findRearrangeButton().vm.$emit('click');
wrapper.vm.$nextTick(() => {
expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
expect(findEnabledDraggables().length).toBe(0);
done();
});
});
});
});
afterEach(() => {
wrapper.destroy();
});
});
// https://gitlab.com/gitlab-org/gitlab-ce/issues/66922
// eslint-disable-next-line jasmine/no-disabled-tests
xdescribe('link to chart', () => {
let wrapper;
......
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