Commit 34dcf767 authored by Miguel Rincon's avatar Miguel Rincon

Renders a single chart from certain URL parameter

When users copy the "link to a chart" they now visit the specific
chart they request instead of the whole dashboard.
parent a9ff2e3a
<script> <script>
import { debounce, pickBy } from 'lodash'; import { debounce } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import VueDraggable from 'vuedraggable'; import VueDraggable from 'vuedraggable';
import { import {
...@@ -32,7 +32,13 @@ import GroupEmptyState from './group_empty_state.vue'; ...@@ -32,7 +32,13 @@ import GroupEmptyState from './group_empty_state.vue';
import DashboardsDropdown from './dashboards_dropdown.vue'; import DashboardsDropdown from './dashboards_dropdown.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event'; import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getAddMetricTrackingOptions, timeRangeToUrl, timeRangeFromUrl } from '../utils'; import {
getAddMetricTrackingOptions,
timeRangeToUrl,
timeRangeFromUrl,
panelToUrl,
expandedPanelPayloadFromUrl,
} from '../utils';
import { metricStates } from '../constants'; import { metricStates } from '../constants';
import { defaultTimeRange, timeRanges } from '~/vue_shared/constants'; import { defaultTimeRange, timeRanges } from '~/vue_shared/constants';
...@@ -238,6 +244,23 @@ export default { ...@@ -238,6 +244,23 @@ export default {
return !this.environmentsLoading && this.filteredEnvironments.length === 0; return !this.environmentsLoading && this.filteredEnvironments.length === 0;
}, },
}, },
watch: {
dashboard(newDashboard) {
try {
const expandedPanel = expandedPanelPayloadFromUrl(newDashboard);
if (expandedPanel) {
this.setExpandedPanel(expandedPanel);
}
} catch {
createFlash(
s__(
'Metrics|Link contains invalid chart information, please verify the link to see the expanded panel.',
),
);
}
},
},
created() { created() {
this.setInitialState({ this.setInitialState({
metricsEndpoint: this.metricsEndpoint, metricsEndpoint: this.metricsEndpoint,
...@@ -299,15 +322,9 @@ export default { ...@@ -299,15 +322,9 @@ export default {
// As a fallback, switch to default time range instead // As a fallback, switch to default time range instead
this.selectedTimeRange = defaultTimeRange; this.selectedTimeRange = defaultTimeRange;
}, },
generatePanelUrl(groupKey, panel) {
generatePanelLink(group, graphData) { const dashboardPath = this.currentDashboard || this.firstDashboard.path;
if (!group || !graphData) { return panelToUrl(dashboardPath, groupKey, panel);
return null;
}
const dashboard = this.currentDashboard || this.firstDashboard.path;
const { y_label, title } = graphData;
const params = pickBy({ dashboard, group, title, y_label }, value => value != null);
return mergeUrlParams(params, window.location.href);
}, },
hideAddMetricModal() { hideAddMetricModal() {
this.$refs.addMetricModal.hide(); this.$refs.addMetricModal.hide();
...@@ -564,7 +581,7 @@ export default { ...@@ -564,7 +581,7 @@ export default {
v-show="expandedPanel.panel" v-show="expandedPanel.panel"
ref="expandedPanel" ref="expandedPanel"
:settings-path="settingsPath" :settings-path="settingsPath"
:clipboard-text="generatePanelLink(expandedPanel.group, expandedPanel.panel)" :clipboard-text="generatePanelUrl(expandedPanel.group, expandedPanel.panel)"
:graph-data="expandedPanel.panel" :graph-data="expandedPanel.panel"
:alerts-endpoint="alertsEndpoint" :alerts-endpoint="alertsEndpoint"
:height="600" :height="600"
...@@ -623,7 +640,7 @@ export default { ...@@ -623,7 +640,7 @@ export default {
<dashboard-panel <dashboard-panel
:settings-path="settingsPath" :settings-path="settingsPath"
:clipboard-text="generatePanelLink(groupData.group, graphData)" :clipboard-text="generatePanelUrl(groupData.group, graphData)"
:graph-data="graphData" :graph-data="graphData"
:alerts-endpoint="alertsEndpoint" :alerts-endpoint="alertsEndpoint"
:prometheus-alerts-available="prometheusAlertsAvailable" :prometheus-alerts-available="prometheusAlertsAvailable"
......
...@@ -102,6 +102,13 @@ export const clearExpandedPanel = ({ commit }) => { ...@@ -102,6 +102,13 @@ export const clearExpandedPanel = ({ commit }) => {
// All Data // All Data
/**
* Fetch all dashboard data.
*
* @param {Object} store
* @returns A promise that resolves when the dashboard
* skeleton has been loaded.
*/
export const fetchData = ({ dispatch }) => { export const fetchData = ({ dispatch }) => {
dispatch('fetchEnvironmentsData'); dispatch('fetchEnvironmentsData');
dispatch('fetchDashboard'); dispatch('fetchDashboard');
......
import { pickBy } from 'lodash';
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility'; import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import { import {
timeRangeParamNames, timeRangeParamNames,
...@@ -28,7 +29,6 @@ export const graphDataValidatorForValues = (isValues, graphData) => { ...@@ -28,7 +29,6 @@ export const graphDataValidatorForValues = (isValues, graphData) => {
); );
}; };
/* eslint-disable @gitlab/require-i18n-strings */
/** /**
* Checks that element that triggered event is located on cluster health check dashboard * Checks that element that triggered event is located on cluster health check dashboard
* @param {HTMLElement} element to check against * @param {HTMLElement} element to check against
...@@ -36,6 +36,7 @@ export const graphDataValidatorForValues = (isValues, graphData) => { ...@@ -36,6 +36,7 @@ export const graphDataValidatorForValues = (isValues, graphData) => {
*/ */
const isClusterHealthBoard = () => (document.body.dataset.page || '').includes(':clusters:show'); const isClusterHealthBoard = () => (document.body.dataset.page || '').includes(':clusters:show');
/* eslint-disable @gitlab/require-i18n-strings */
/** /**
* Tracks snowplow event when user generates link to metric chart * Tracks snowplow event when user generates link to metric chart
* @param {String} chart link that will be sent as a property for the event * @param {String} chart link that will be sent as a property for the event
...@@ -71,6 +72,7 @@ export const downloadCSVOptions = title => { ...@@ -71,6 +72,7 @@ export const downloadCSVOptions = title => {
return { category, action, label: 'Chart title', property: title }; return { category, action, label: 'Chart title', property: title };
}; };
/* eslint-enable @gitlab/require-i18n-strings */
/** /**
* Generate options for snowplow to track adding a new metric via the dashboard * Generate options for snowplow to track adding a new metric via the dashboard
...@@ -132,6 +134,68 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => { ...@@ -132,6 +134,68 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => {
return mergeUrlParams(params, toUrl); return mergeUrlParams(params, toUrl);
}; };
/**
* Locates a panel (and its corresponding group) given a (URL) search query. Returns
* it as payload for the store to set the right expandaded panel.
*
* Params used to locate a panel are:
* - group: Group identifier
* - title: Panel title
* - y_label: Panel y_label
*
* @param {Object} dashboard - Dashboard reference from the Vuex store
* @param {String} search - URL location search query
* @returns {Object} payload - Payload for expanded panel to be displayed
* @returns {String} payload.group - Group where panel is located
* @returns {Object} payload.panel - Dashboard panel (graphData) reference
* @throws Will throw an error if Panel cannot be located.
*/
export const expandedPanelPayloadFromUrl = (dashboard, search = window.location.search) => {
const params = queryToObject(search);
// Search for the panel if any of the search params is identified
if (params.group || params.title || params.y_label) {
const panelGroup = dashboard.panelGroups.find(({ group }) => params.group === group);
const panel = panelGroup.panels.find(
// eslint-disable-next-line babel/camelcase
({ y_label, title }) => y_label === params.y_label && title === params.title,
);
if (!panel) {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Panel could no found by URL parameters.');
}
return { group: panelGroup.group, panel };
}
return null;
};
/**
* Convert panel information to a URL for the user to
* bookmark or share highlighting a specific panel.
*
* @param {String} dashboardPath - Dashboard path used as identifier
* @param {String} group - Group Identifier
* @param {?Object} panel - Panel object from the dashboard
* @param {?String} url - Base URL including current search params
* @returns Dashboard URL which expands a panel (chart)
*/
export const panelToUrl = (dashboardPath, group, panel, url = window.location.href) => {
if (!group || !panel) {
return null;
}
const params = pickBy(
{
dashboard: dashboardPath,
group,
title: panel.title,
y_label: panel.y_label,
},
value => value != null,
);
return mergeUrlParams(params, url);
};
/** /**
* Get the metric value from first data point. * Get the metric value from first data point.
* Currently only used for bar charts * Currently only used for bar charts
......
---
title: Display expanded dashboard from a panel's "Link to chart" URL
merge_request: 30476
author:
type: added
...@@ -13266,6 +13266,9 @@ msgstr "" ...@@ -13266,6 +13266,9 @@ msgstr ""
msgid "Metrics|Link contains an invalid time window, please verify the link to see the requested time range." msgid "Metrics|Link contains an invalid time window, please verify the link to see the requested time range."
msgstr "" msgstr ""
msgid "Metrics|Link contains invalid chart information, please verify the link to see the expanded panel."
msgstr ""
msgid "Metrics|Max" msgid "Metrics|Max"
msgstr "" msgstr ""
......
...@@ -376,10 +376,6 @@ describe('Dashboard Panel', () => { ...@@ -376,10 +376,6 @@ describe('Dashboard Panel', () => {
}); });
}); });
afterEach(() => {
wrapper.destroy();
});
it('sets clipboard text on the dropdown', () => { it('sets clipboard text on the dropdown', () => {
expect(findCopyLink().exists()).toBe(true); expect(findCopyLink().exists()).toBe(true);
expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText); expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText);
...@@ -396,6 +392,18 @@ describe('Dashboard Panel', () => { ...@@ -396,6 +392,18 @@ describe('Dashboard Panel', () => {
}); });
}); });
describe('when cliboard data is not available', () => {
it('there is no "copy to clipboard" link for a null value', () => {
createWrapper({ clipboardText: null });
expect(findCopyLink().exists()).toBe(false);
});
it('there is no "copy to clipboard" link for an empty value', () => {
createWrapper({ clipboardText: '' });
expect(findCopyLink().exists()).toBe(false);
});
});
describe('when downloading metrics data as CSV', () => { describe('when downloading metrics data as CSV', () => {
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(DashboardPanel, { wrapper = shallowMount(DashboardPanel, {
......
...@@ -2,6 +2,7 @@ import { shallowMount, mount } from '@vue/test-utils'; ...@@ -2,6 +2,7 @@ import { shallowMount, mount } from '@vue/test-utils';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys'; import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui'; import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui';
import { objectToQuery } from '~/lib/utils/url_utility';
import VueDraggable from 'vuedraggable'; import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -20,6 +21,9 @@ import * as types from '~/monitoring/stores/mutation_types'; ...@@ -20,6 +21,9 @@ import * as types from '~/monitoring/stores/mutation_types';
import { setupStoreWithDashboard, setMetricResult, setupStoreWithData } from '../store_utils'; import { setupStoreWithDashboard, setMetricResult, setupStoreWithData } from '../store_utils';
import { environmentData, dashboardGitResponse, propsData } from '../mock_data'; import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data'; import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
import createFlash from '~/flash';
jest.mock('~/flash');
describe('Dashboard', () => { describe('Dashboard', () => {
let store; let store;
...@@ -64,7 +68,6 @@ describe('Dashboard', () => { ...@@ -64,7 +68,6 @@ describe('Dashboard', () => {
describe('no metrics are available yet', () => { describe('no metrics are available yet', () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(store, 'dispatch');
createShallowWrapper(); createShallowWrapper();
}); });
...@@ -150,6 +153,87 @@ describe('Dashboard', () => { ...@@ -150,6 +153,87 @@ describe('Dashboard', () => {
}); });
}); });
describe('when the URL contains a reference to a panel', () => {
let location;
const setSearch = search => {
window.location = { ...location, search };
};
beforeEach(() => {
location = window.location;
delete window.location;
});
afterEach(() => {
window.location = location;
});
it('when the URL points to a panel it expands', () => {
const panelGroup = metricsDashboardViewModel.panelGroups[0];
const panel = panelGroup.panels[0];
setSearch(
objectToQuery({
group: panelGroup.group,
title: panel.title,
y_label: panel.y_label,
}),
);
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
group: panelGroup.group,
panel: expect.objectContaining({
title: panel.title,
y_label: panel.y_label,
}),
});
});
});
it('when the URL does not link to any panel, no panel is expanded', () => {
setSearch('');
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).not.toHaveBeenCalledWith(
'monitoringDashboard/setExpandedPanel',
expect.anything(),
);
});
});
it('when the URL points to an incorrect panel it shows an error', () => {
const panelGroup = metricsDashboardViewModel.panelGroups[0];
const panel = panelGroup.panels[0];
setSearch(
objectToQuery({
group: panelGroup.group,
title: 'incorrect',
y_label: panel.y_label,
}),
);
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
return wrapper.vm.$nextTick().then(() => {
expect(createFlash).toHaveBeenCalled();
expect(store.dispatch).not.toHaveBeenCalledWith(
'monitoringDashboard/setExpandedPanel',
expect.anything(),
);
});
});
});
describe('when all requests have been commited by the store', () => { describe('when all requests have been commited by the store', () => {
beforeEach(() => { beforeEach(() => {
createMountedWrapper({ hasMetrics: true }); createMountedWrapper({ hasMetrics: true });
......
...@@ -11,6 +11,7 @@ import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; ...@@ -11,6 +11,7 @@ import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
import store from '~/monitoring/stores'; import store from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
import { import {
fetchData,
fetchDashboard, fetchDashboard,
receiveMetricsDashboardSuccess, receiveMetricsDashboardSuccess,
fetchDeploymentsData, fetchDeploymentsData,
...@@ -86,6 +87,41 @@ describe('Monitoring store actions', () => { ...@@ -86,6 +87,41 @@ describe('Monitoring store actions', () => {
createFlash.mockReset(); createFlash.mockReset();
}); });
describe('fetchData', () => {
it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
const { state } = store;
return testAction(
fetchData,
null,
state,
[],
[{ type: 'fetchEnvironmentsData' }, { type: 'fetchDashboard' }],
);
});
it('dispatches when feature metricsDashboardAnnotations is on', () => {
const origGon = window.gon;
window.gon = { features: { metricsDashboardAnnotations: true } };
const { state } = store;
return testAction(
fetchData,
null,
state,
[],
[
{ type: 'fetchEnvironmentsData' },
{ type: 'fetchDashboard' },
{ type: 'fetchAnnotations' },
],
).then(() => {
window.gon = origGon;
});
});
});
describe('fetchDeploymentsData', () => { describe('fetchDeploymentsData', () => {
it('dispatches receiveDeploymentsDataSuccess on success', () => { it('dispatches receiveDeploymentsDataSuccess on success', () => {
const { state } = store; const { state } = store;
......
import * as monitoringUtils from '~/monitoring/utils'; import * as monitoringUtils from '~/monitoring/utils';
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import { TEST_HOST } from 'jest/helpers/test_constants'; import { TEST_HOST } from 'jest/helpers/test_constants';
import { import {
mockProjectDir, mockProjectDir,
...@@ -7,9 +7,7 @@ import { ...@@ -7,9 +7,7 @@ import {
anomalyMockGraphData, anomalyMockGraphData,
barMockData, barMockData,
} from './mock_data'; } from './mock_data';
import { graphData } from './fixture_data'; import { metricsDashboardViewModel, graphData } from './fixture_data';
jest.mock('~/lib/utils/url_utility');
const mockPath = `${TEST_HOST}${mockProjectDir}/-/environments/29/metrics`; const mockPath = `${TEST_HOST}${mockProjectDir}/-/environments/29/metrics`;
...@@ -27,11 +25,6 @@ const rollingRange = { ...@@ -27,11 +25,6 @@ const rollingRange = {
}; };
describe('monitoring/utils', () => { describe('monitoring/utils', () => {
afterEach(() => {
mergeUrlParams.mockReset();
queryToObject.mockReset();
});
describe('trackGenerateLinkToChartEventOptions', () => { describe('trackGenerateLinkToChartEventOptions', () => {
it('should return Cluster Monitoring options if located on Cluster Health Dashboard', () => { it('should return Cluster Monitoring options if located on Cluster Health Dashboard', () => {
document.body.dataset.page = 'groups:clusters:show'; document.body.dataset.page = 'groups:clusters:show';
...@@ -139,18 +132,25 @@ describe('monitoring/utils', () => { ...@@ -139,18 +132,25 @@ describe('monitoring/utils', () => {
}); });
describe('timeRangeFromUrl', () => { describe('timeRangeFromUrl', () => {
const { timeRangeFromUrl } = monitoringUtils; beforeEach(() => {
jest.spyOn(urlUtils, 'queryToObject');
});
it('returns a fixed range when query contains `start` and `end` paramters are given', () => { afterEach(() => {
queryToObject.mockReturnValueOnce(range); urlUtils.queryToObject.mockRestore();
});
const { timeRangeFromUrl } = monitoringUtils;
it('returns a fixed range when query contains `start` and `end` parameters are given', () => {
urlUtils.queryToObject.mockReturnValueOnce(range);
expect(timeRangeFromUrl()).toEqual(range); expect(timeRangeFromUrl()).toEqual(range);
}); });
it('returns a rolling range when query contains `duration_seconds` paramters are given', () => { it('returns a rolling range when query contains `duration_seconds` parameters are given', () => {
const { seconds } = rollingRange.duration; const { seconds } = rollingRange.duration;
queryToObject.mockReturnValueOnce({ urlUtils.queryToObject.mockReturnValueOnce({
dashboard: '.gitlab/dashboard/my_dashboard.yml', dashboard: '.gitlab/dashboard/my_dashboard.yml',
duration_seconds: `${seconds}`, duration_seconds: `${seconds}`,
}); });
...@@ -158,23 +158,21 @@ describe('monitoring/utils', () => { ...@@ -158,23 +158,21 @@ describe('monitoring/utils', () => {
expect(timeRangeFromUrl()).toEqual(rollingRange); expect(timeRangeFromUrl()).toEqual(rollingRange);
}); });
it('returns null when no time range paramters are given', () => { it('returns null when no time range parameters are given', () => {
const params = { urlUtils.queryToObject.mockReturnValueOnce({
dashboard: '.gitlab/dashboards/custom_dashboard.yml', dashboard: '.gitlab/dashboards/custom_dashboard.yml',
param1: 'value1', param1: 'value1',
param2: 'value2', param2: 'value2',
}; });
expect(timeRangeFromUrl(params, mockPath)).toBe(null); expect(timeRangeFromUrl()).toBe(null);
}); });
}); });
describe('removeTimeRangeParams', () => { describe('removeTimeRangeParams', () => {
const { removeTimeRangeParams } = monitoringUtils; const { removeTimeRangeParams } = monitoringUtils;
it('returns when query contains `start` and `end` paramters are given', () => { it('returns when query contains `start` and `end` parameters are given', () => {
removeParams.mockReturnValueOnce(mockPath);
expect(removeTimeRangeParams(`${mockPath}?start=${range.start}&end=${range.end}`)).toEqual( expect(removeTimeRangeParams(`${mockPath}?start=${range.start}&end=${range.end}`)).toEqual(
mockPath, mockPath,
); );
...@@ -184,28 +182,116 @@ describe('monitoring/utils', () => { ...@@ -184,28 +182,116 @@ describe('monitoring/utils', () => {
describe('timeRangeToUrl', () => { describe('timeRangeToUrl', () => {
const { timeRangeToUrl } = monitoringUtils; const { timeRangeToUrl } = monitoringUtils;
it('returns a fixed range when query contains `start` and `end` paramters are given', () => { beforeEach(() => {
jest.spyOn(urlUtils, 'mergeUrlParams');
jest.spyOn(urlUtils, 'removeParams');
});
afterEach(() => {
urlUtils.mergeUrlParams.mockRestore();
urlUtils.removeParams.mockRestore();
});
it('returns a fixed range when query contains `start` and `end` parameters are given', () => {
const toUrl = `${mockPath}?start=${range.start}&end=${range.end}`; const toUrl = `${mockPath}?start=${range.start}&end=${range.end}`;
const fromUrl = mockPath; const fromUrl = mockPath;
removeParams.mockReturnValueOnce(fromUrl); urlUtils.removeParams.mockReturnValueOnce(fromUrl);
mergeUrlParams.mockReturnValueOnce(toUrl); urlUtils.mergeUrlParams.mockReturnValueOnce(toUrl);
expect(timeRangeToUrl(range)).toEqual(toUrl); expect(timeRangeToUrl(range)).toEqual(toUrl);
expect(mergeUrlParams).toHaveBeenCalledWith(range, fromUrl); expect(urlUtils.mergeUrlParams).toHaveBeenCalledWith(range, fromUrl);
}); });
it('returns a rolling range when query contains `duration_seconds` paramters are given', () => { it('returns a rolling range when query contains `duration_seconds` parameters are given', () => {
const { seconds } = rollingRange.duration; const { seconds } = rollingRange.duration;
const toUrl = `${mockPath}?duration_seconds=${seconds}`; const toUrl = `${mockPath}?duration_seconds=${seconds}`;
const fromUrl = mockPath; const fromUrl = mockPath;
removeParams.mockReturnValueOnce(fromUrl); urlUtils.removeParams.mockReturnValueOnce(fromUrl);
mergeUrlParams.mockReturnValueOnce(toUrl); urlUtils.mergeUrlParams.mockReturnValueOnce(toUrl);
expect(timeRangeToUrl(rollingRange)).toEqual(toUrl); expect(timeRangeToUrl(rollingRange)).toEqual(toUrl);
expect(mergeUrlParams).toHaveBeenCalledWith({ duration_seconds: `${seconds}` }, fromUrl); expect(urlUtils.mergeUrlParams).toHaveBeenCalledWith(
{ duration_seconds: `${seconds}` },
fromUrl,
);
});
});
describe('expandedPanelPayloadFromUrl', () => {
const { expandedPanelPayloadFromUrl } = monitoringUtils;
const [panelGroup] = metricsDashboardViewModel.panelGroups;
const [panel] = panelGroup.panels;
const { group } = panelGroup;
const { title, y_label: yLabel } = panel;
it('returns payload for a panel when query parameters are given', () => {
const search = `?group=${group}&title=${title}&y_label=${yLabel}`;
expect(expandedPanelPayloadFromUrl(metricsDashboardViewModel, search)).toEqual({
group: panelGroup.group,
panel,
});
});
it('returns null when no parameters are given', () => {
expect(expandedPanelPayloadFromUrl(metricsDashboardViewModel, '')).toBe(null);
});
it('throws an error when no group is provided', () => {
const search = `?title=${panel.title}&y_label=${yLabel}`;
expect(() => expandedPanelPayloadFromUrl(metricsDashboardViewModel, search)).toThrow();
});
it('throws an error when no title is provided', () => {
const search = `?title=${title}&y_label=${yLabel}`;
expect(() => expandedPanelPayloadFromUrl(metricsDashboardViewModel, search)).toThrow();
});
it('throws an error when no y_label group is provided', () => {
const search = `?group=${group}&title=${title}`;
expect(() => expandedPanelPayloadFromUrl(metricsDashboardViewModel, search)).toThrow();
});
test.each`
group | title | yLabel | missingField
${'NOT_A_GROUP'} | ${title} | ${yLabel} | ${'group'}
${group} | ${'NOT_A_TITLE'} | ${yLabel} | ${'title'}
${group} | ${title} | ${'NOT_A_Y_LABEL'} | ${'y_label'}
`('throws an error when $missingField is incorrect', params => {
const search = `?group=${params.group}&title=${params.title}&y_label=${params.yLabel}`;
expect(() => expandedPanelPayloadFromUrl(metricsDashboardViewModel, search)).toThrow();
});
});
describe('panelToUrl', () => {
const { panelToUrl } = monitoringUtils;
const dashboard = 'metrics.yml';
const [panelGroup] = metricsDashboardViewModel.panelGroups;
const [panel] = panelGroup.panels;
it('returns URL for a panel when query parameters are given', () => {
const [, query] = panelToUrl(dashboard, panelGroup.group, panel).split('?');
const params = urlUtils.queryToObject(query);
expect(params).toEqual({
dashboard,
group: panelGroup.group,
title: panel.title,
y_label: panel.y_label,
});
});
it('returns `null` if group is missing', () => {
expect(panelToUrl(dashboard, null, panel)).toBe(null);
});
it('returns `null` if panel is missing', () => {
expect(panelToUrl(dashboard, panelGroup.group, null)).toBe(null);
}); });
}); });
......
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