Add clipboard button to metric chart dropdown
Adds a clipboard button to the metrics dashboard, that allows copying a link to an individual chart.
This commit is contained in:
parent
535c2d3c71
commit
9cba187ac1
7 changed files with 139 additions and 5 deletions
|
@ -10,9 +10,9 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import _ from 'underscore';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { s__ } from '~/locale';
|
||||
import { __, s__ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import invalidUrl from '~/lib/utils/invalid_url';
|
||||
import MonitorAreaChart from './charts/area.vue';
|
||||
import MonitorSingleStatChart from './charts/single_stat.vue';
|
||||
|
@ -168,8 +168,11 @@ export default {
|
|||
'multipleDashboardsEnabled',
|
||||
'additionalPanelTypesEnabled',
|
||||
]),
|
||||
firstDashboard() {
|
||||
return this.allDashboards[0] || {};
|
||||
},
|
||||
selectedDashboardText() {
|
||||
return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name);
|
||||
return this.currentDashboard || this.firstDashboard.display_name;
|
||||
},
|
||||
addingMetricsAvailable() {
|
||||
return IS_EE && this.canAddMetrics && !this.showEmptyState;
|
||||
|
@ -258,6 +261,14 @@ export default {
|
|||
getGraphAlertValues(queries) {
|
||||
return Object.values(this.getGraphAlerts(queries));
|
||||
},
|
||||
showToast() {
|
||||
this.$toast.show(__('Link copied to clipboard'));
|
||||
},
|
||||
generateLink(group, title, yLabel) {
|
||||
const dashboard = this.currentDashboard || this.firstDashboard.path;
|
||||
const params = { dashboard, group, title, y_label: yLabel };
|
||||
return mergeUrlParams(params, window.location.href);
|
||||
},
|
||||
// TODO: END
|
||||
hideAddMetricModal() {
|
||||
this.$refs.addMetricModal.hide();
|
||||
|
@ -435,6 +446,7 @@ export default {
|
|||
<panel-type
|
||||
v-for="(graphData, graphIndex) in groupData.metrics"
|
||||
:key="`panel-type-${graphIndex}`"
|
||||
:clipboard-text="generateLink(groupData.group, graphData.title, graphData.y_label)"
|
||||
:graph-data="graphData"
|
||||
:dashboard-width="elWidth"
|
||||
:index="`${index}-${graphIndex}`"
|
||||
|
@ -474,6 +486,15 @@ export default {
|
|||
<gl-dropdown-item :href="downloadCsv(graphData)" download="chart_metrics.csv">
|
||||
{{ __('Download CSV') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item
|
||||
class="js-chart-link"
|
||||
:data-clipboard-text="
|
||||
generateLink(groupData.group, graphData.title, graphData.y_label)
|
||||
"
|
||||
@click="showToast"
|
||||
>
|
||||
{{ __('Generate link to chart') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item
|
||||
v-if="alertWidgetAvailable"
|
||||
v-gl-modal="`alert-modal-${index}-${graphIndex}`"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import _ from 'underscore';
|
||||
import { __ } from '~/locale';
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
|
@ -28,6 +29,10 @@ export default {
|
|||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
clipboardText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
graphData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -76,6 +81,9 @@ export default {
|
|||
isPanelType(type) {
|
||||
return this.graphData.type && this.graphData.type === type;
|
||||
},
|
||||
showToast() {
|
||||
this.$toast.show(__('Link copied to clipboard'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -116,6 +124,13 @@ export default {
|
|||
<gl-dropdown-item :href="downloadCsv" download="chart_metrics.csv">
|
||||
{{ __('Download CSV') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item
|
||||
class="js-chart-link"
|
||||
:data-clipboard-text="clipboardText"
|
||||
@click="showToast"
|
||||
>
|
||||
{{ __('Generate link to chart') }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`">
|
||||
{{ __('Alerts') }}
|
||||
</gl-dropdown-item>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import Vue from 'vue';
|
||||
import { GlToast } from '@gitlab/ui';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
|
||||
import store from './stores';
|
||||
|
||||
Vue.use(GlToast);
|
||||
|
||||
export default (props = {}) => {
|
||||
const el = document.getElementById('prometheus-graphs');
|
||||
|
||||
|
|
5
changelogs/unreleased/tr-embed-metric-links.yml
Normal file
5
changelogs/unreleased/tr-embed-metric-links.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Generate shareable link for specific metric charts
|
||||
merge_request: 31339
|
||||
author:
|
||||
type: added
|
|
@ -5181,6 +5181,9 @@ msgstr ""
|
|||
msgid "Generate a default set of labels"
|
||||
msgstr ""
|
||||
|
||||
msgid "Generate link to chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Generate new export"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6534,6 +6537,9 @@ msgid_plural "Limited to showing %d events at most"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Link copied to clipboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Linked emails (%{email_count})"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import { GlToast } from '@gitlab/ui';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Dashboard from '~/monitoring/components/dashboard.vue';
|
||||
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
|
||||
|
@ -13,6 +15,7 @@ import MonitoringMock, {
|
|||
dashboardGitResponse,
|
||||
} from './mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
const propsData = {
|
||||
hasMetrics: false,
|
||||
documentationPath: '/path/to/docs',
|
||||
|
@ -59,7 +62,9 @@ describe('Dashboard', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.$destroy();
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
}
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
|
@ -373,6 +378,51 @@ describe('Dashboard', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('link to chart', () => {
|
||||
let wrapper;
|
||||
const currentDashboard = 'TEST_DASHBOARD';
|
||||
localVue.use(GlToast);
|
||||
const link = () => wrapper.find('.js-chart-link');
|
||||
const clipboardText = () => link().element.dataset.clipboardText;
|
||||
|
||||
beforeEach(done => {
|
||||
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
|
||||
|
||||
wrapper = shallowMount(DashboardComponent, {
|
||||
localVue,
|
||||
sync: false,
|
||||
attachToDocument: true,
|
||||
propsData: { ...propsData, hasMetrics: true, currentDashboard },
|
||||
store,
|
||||
});
|
||||
|
||||
setTimeout(done);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('adds a copy button to the dropdown', () => {
|
||||
expect(link().text()).toContain('Generate link to chart');
|
||||
});
|
||||
|
||||
it('contains a link to the dashboard', () => {
|
||||
expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
|
||||
expect(clipboardText()).toContain(`group=`);
|
||||
expect(clipboardText()).toContain(`title=`);
|
||||
expect(clipboardText()).toContain(`y_label=`);
|
||||
});
|
||||
|
||||
it('creates a toast when clicked', () => {
|
||||
spyOn(wrapper.vm.$toast, 'show').and.stub();
|
||||
|
||||
link().vm.$emit('click');
|
||||
|
||||
expect(wrapper.vm.$toast.show).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the window resizes', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import PanelType from '~/monitoring/components/panel_type.vue';
|
||||
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
|
||||
import AreaChart from '~/monitoring/components/charts/area.vue';
|
||||
import { graphDataPrometheusQueryRange } from './mock_data';
|
||||
import { createStore } from '~/monitoring/stores';
|
||||
|
||||
describe('Panel Type component', () => {
|
||||
let store;
|
||||
let panelType;
|
||||
const dashboardWidth = 100;
|
||||
|
||||
describe('When no graphData is available', () => {
|
||||
let glEmptyChart;
|
||||
const graphDataNoResult = graphDataPrometheusQueryRange;
|
||||
// Deep clone object before modifying
|
||||
const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
|
||||
graphDataNoResult.queries[0].result = [];
|
||||
|
||||
beforeEach(() => {
|
||||
panelType = shallowMount(PanelType, {
|
||||
propsData: {
|
||||
clipboardText: 'dashboard_link',
|
||||
dashboardWidth,
|
||||
graphData: graphDataNoResult,
|
||||
},
|
||||
|
@ -41,4 +46,33 @@ describe('Panel Type component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Graph data is available', () => {
|
||||
const exampleText = 'example_text';
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
panelType = shallowMount(PanelType, {
|
||||
propsData: {
|
||||
clipboardText: exampleText,
|
||||
dashboardWidth,
|
||||
graphData: graphDataPrometheusQueryRange,
|
||||
},
|
||||
store,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Area Chart panel type', () => {
|
||||
it('is rendered', () => {
|
||||
expect(panelType.find(AreaChart).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sets clipboard text on the dropdown', () => {
|
||||
const link = () => panelType.find('.js-chart-link');
|
||||
const clipboardText = () => link().element.dataset.clipboardText;
|
||||
|
||||
expect(clipboardText()).toBe(exampleText);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue