summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Read <tread@gitlab.com>2019-08-16 16:21:46 +0000
committerFilipa Lacerda <filipa@gitlab.com>2019-08-16 16:21:46 +0000
commit9cba187ac118278467941fcf497cbc57d4121f94 (patch)
treed722e9b2e1e5d32b4c59a61be2ff45bc59921db9
parent535c2d3c71c1cc623958a48f429a88ecaafb2702 (diff)
downloadgitlab-ce-9cba187ac118278467941fcf497cbc57d4121f94.tar.gz
Add clipboard button to metric chart dropdown
Adds a clipboard button to the metrics dashboard, that allows copying a link to an individual chart.
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue27
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue15
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js3
-rw-r--r--changelogs/unreleased/tr-embed-metric-links.yml5
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js52
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js36
7 files changed, 139 insertions, 5 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 587392adbc3..dfeeba238ca 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -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}`"
@@ -475,6 +487,15 @@ export default {
{{ __('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}`"
>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 3fbac71f3d7..96f62bc85ee 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -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>
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index c0fee1ebb99..51cef20455c 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -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');
diff --git a/changelogs/unreleased/tr-embed-metric-links.yml b/changelogs/unreleased/tr-embed-metric-links.yml
new file mode 100644
index 00000000000..6918114a4ae
--- /dev/null
+++ b/changelogs/unreleased/tr-embed-metric-links.yml
@@ -0,0 +1,5 @@
+---
+title: Generate shareable link for specific metric charts
+merge_request: 31339
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d85b514ecfb..afdfd620ca2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index b78896c45fc..624d8b14c8f 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -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);
diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
index 8ce24041e97..086be628093 100644
--- a/spec/javascripts/monitoring/panel_type_spec.js
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -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);
+ });
+ });
+ });
});