summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js4
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue34
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue18
-rw-r--r--app/assets/javascripts/monitoring/constants.js22
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js2
-rw-r--r--app/assets/javascripts/monitoring/utils.js45
-rw-r--r--changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml5
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js35
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js16
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js4
-rw-r--r--spec/javascripts/monitoring/utils_spec.js28
11 files changed, 141 insertions, 72 deletions
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 32fd0990374..1336b6a5461 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -2,8 +2,8 @@ import { join as joinPaths } from 'path';
// Returns an array containing the value(s) of the
// of the key passed as an argument
-export function getParameterValues(sParam) {
- const sPageURL = decodeURIComponent(window.location.search.substring(1));
+export function getParameterValues(sParam, url = window.location) {
+ const sPageURL = decodeURIComponent(new URL(url).search.substring(1));
return sPageURL.split('&').reduce((acc, urlParam) => {
const sParameterName = urlParam.split('=');
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 2feb545199b..5892c18ac91 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -18,8 +18,8 @@ import MonitorSingleStatChart from './charts/single_stat.vue';
import PanelType from './panel_type.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
-import { sidebarAnimationDuration, timeWindows, timeWindowsKeyNames } from '../constants';
-import { getTimeDiff } from '../utils';
+import { sidebarAnimationDuration, timeWindows } from '../constants';
+import { getTimeDiff, getTimeWindow } from '../utils';
let sidebarMutationObserver;
@@ -147,6 +147,7 @@ export default {
selectedTimeWindow: '',
selectedTimeWindowKey: '',
formIsValid: null,
+ timeWindows: {},
};
},
computed: {
@@ -184,17 +185,6 @@ export default {
currentDashboard: this.currentDashboard,
projectPath: this.projectPath,
});
-
- this.timeWindows = timeWindows;
- this.selectedTimeWindowKey =
- _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours;
-
- // Set default time window if the selectedTimeWindowKey is bogus
- if (!Object.keys(this.timeWindows).includes(this.selectedTimeWindowKey)) {
- this.selectedTimeWindowKey = timeWindowsKeyNames.eightHours;
- }
-
- this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
},
beforeDestroy() {
if (sidebarMutationObserver) {
@@ -205,7 +195,20 @@ export default {
if (!this.hasMetrics) {
this.setGettingStartedEmptyState();
} else {
- this.fetchData(getTimeDiff(this.selectedTimeWindow));
+ const defaultRange = getTimeDiff();
+ const start = getParameterValues('start')[0] || defaultRange.start;
+ const end = getParameterValues('end')[0] || defaultRange.end;
+
+ const range = {
+ start,
+ end,
+ };
+
+ this.timeWindows = timeWindows;
+ this.selectedTimeWindowKey = getTimeWindow(range);
+ this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
+
+ this.fetchData(range);
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
@@ -259,7 +262,8 @@ export default {
return this.timeWindows[key] === this.selectedTimeWindow;
},
setTimeWindowParameter(key) {
- return `?time_window=${key}`;
+ const { start, end } = getTimeDiff(key);
+ return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
},
groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0;
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index e17f03de0fd..9e85b0633fe 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -1,8 +1,9 @@
<script>
import { mapActions, mapState } from 'vuex';
+import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import GraphGroup from './graph_group.vue';
import MonitorAreaChart from './charts/area.vue';
-import { sidebarAnimationDuration, timeWindowsKeyNames, timeWindows } from '../constants';
+import { sidebarAnimationDuration } from '../constants';
import { getTimeDiff } from '../utils';
let sidebarMutationObserver;
@@ -19,10 +20,17 @@ export default {
},
},
data() {
+ const defaultRange = getTimeDiff();
+ const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start;
+ const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end;
+
+ const params = {
+ start,
+ end,
+ };
+
return {
- params: {
- ...getTimeDiff(timeWindows[timeWindowsKeyNames.eightHours]),
- },
+ params,
elWidth: 0,
};
},
@@ -73,7 +81,7 @@ export default {
prometheusEndpointEnabled: true,
});
this.setEndpoints({
- dashboardEndpoint: this.dashboardUrl,
+ dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl),
});
this.setShowErrorBanner(false);
},
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 605c95e6da5..d7d89522732 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -21,11 +21,19 @@ export const timeWindows = {
oneWeek: __('1 week'),
};
-export const timeWindowsKeyNames = {
- thirtyMinutes: 'thirtyMinutes',
- threeHours: 'threeHours',
- eightHours: 'eightHours',
- oneDay: 'oneDay',
- threeDays: 'threeDays',
- oneWeek: 'oneWeek',
+export const secondsIn = {
+ thirtyMinutes: 60 * 30,
+ threeHours: 60 * 60 * 3,
+ eightHours: 60 * 60 * 8,
+ oneDay: 60 * 60 * 24 * 1,
+ threeDays: 60 * 60 * 24 * 3,
+ oneWeek: 60 * 60 * 24 * 7 * 1,
};
+
+export const timeWindowsKeyNames = Object.keys(secondsIn).reduce(
+ (otherTimeWindows, timeWindow) => ({
+ ...otherTimeWindows,
+ [timeWindow]: timeWindow,
+ }),
+ {},
+);
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 245cc2eaca3..0cbad179f17 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -151,7 +151,7 @@ function fetchPrometheusResult(prometheusEndpoint, params) {
*/
export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
const { start, end } = params;
- const timeDiff = end - start;
+ const timeDiff = (new Date(end) - new Date(start)) / 1000;
const minStep = 60;
const queryDataPoints = 600;
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 478e2b3d06c..46b01f753f8 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -1,35 +1,24 @@
-import { timeWindows } from './constants';
+import { secondsIn, timeWindowsKeyNames } from './constants';
-/**
- * method that converts a predetermined time window to minutes
- * defaults to 8 hours as the default option
- * @param {String} timeWindow - The time window to convert to minutes
- * @returns {number} The time window in minutes
- */
-const getTimeDifferenceSeconds = timeWindow => {
- switch (timeWindow) {
- case timeWindows.thirtyMinutes:
- return 60 * 30;
- case timeWindows.threeHours:
- return 60 * 60 * 3;
- case timeWindows.oneDay:
- return 60 * 60 * 24 * 1;
- case timeWindows.threeDays:
- return 60 * 60 * 24 * 3;
- case timeWindows.oneWeek:
- return 60 * 60 * 24 * 7 * 1;
- default:
- return 60 * 60 * 8;
- }
-};
+export const getTimeDiff = timeWindow => {
+ const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
+ const difference = secondsIn[timeWindow] || secondsIn.eightHours;
+ const start = end - difference;
-export const getTimeDiff = selectedTimeWindow => {
- const end = Date.now() / 1000; // convert milliseconds to seconds
- const start = end - getTimeDifferenceSeconds(selectedTimeWindow);
-
- return { start, end };
+ return {
+ start: new Date(start * 1000).toISOString(),
+ end: new Date(end * 1000).toISOString(),
+ };
};
+export const getTimeWindow = ({ start, end }) =>
+ Object.entries(secondsIn).reduce((acc, [timeRange, value]) => {
+ if (end - start === value) {
+ return timeRange;
+ }
+ return acc;
+ }, timeWindowsKeyNames.eightHours);
+
/**
* This method is used to validate if the graph data format for a chart component
* that needs a time series as a response from a prometheus query (query_range) is
diff --git a/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml
new file mode 100644
index 00000000000..aaf0ddfa48d
--- /dev/null
+++ b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Allow links to metrics dashboard at a specific time
+merge_request: 31283
+author:
+type: added
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index c771984a137..a986bc49f28 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -34,6 +34,41 @@ describe('URL utility', () => {
});
});
+ describe('getParameterValues', () => {
+ beforeEach(() => {
+ setWindowLocation({
+ href: 'https://gitlab.com?test=passing&multiple=1&multiple=2',
+ // make our fake location act like real window.location.toString
+ // URL() (used in getParameterValues) does this if passed an object
+ toString() {
+ return this.href;
+ },
+ });
+ });
+
+ it('returns empty array for no params', () => {
+ expect(urlUtils.getParameterValues()).toEqual([]);
+ });
+
+ it('returns empty array for non-matching params', () => {
+ expect(urlUtils.getParameterValues('notFound')).toEqual([]);
+ });
+
+ it('returns single match', () => {
+ expect(urlUtils.getParameterValues('test')).toEqual(['passing']);
+ });
+
+ it('returns multiple matches', () => {
+ expect(urlUtils.getParameterValues('multiple')).toEqual(['1', '2']);
+ });
+
+ it('accepts url as second arg', () => {
+ const url = 'https://gitlab.com?everything=works';
+ expect(urlUtils.getParameterValues('everything', url)).toEqual(['works']);
+ expect(urlUtils.getParameterValues('test', url)).toEqual([]);
+ });
+ });
+
describe('mergeUrlParams', () => {
it('adds w', () => {
expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index d3e10194d92..36f650d5933 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -307,7 +307,7 @@ describe('Dashboard', () => {
});
spyOn(component.$store, 'dispatch').and.stub();
- const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough();
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
@@ -319,7 +319,7 @@ describe('Dashboard', () => {
Vue.nextTick()
.then(() => {
expect(component.$store.dispatch).toHaveBeenCalled();
- expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
+ expect(getTimeDiffSpy).toHaveBeenCalled();
done();
})
@@ -327,7 +327,17 @@ describe('Dashboard', () => {
});
it('shows a specific time window selected from the url params', done => {
- spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
+ const start = 1564439536;
+ const end = 1564441336;
+ spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({
+ start,
+ end,
+ });
+ spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => {
+ if (param === 'start') return [start];
+ if (param === 'end') return [end];
+ return [];
+ });
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
index 677455275de..955a39e03a5 100644
--- a/spec/javascripts/monitoring/store/actions_spec.js
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -313,8 +313,8 @@ describe('Monitoring store actions', () => {
it('commits prometheus query result', done => {
const commit = jasmine.createSpy();
const params = {
- start: '1557216349.469',
- end: '1557218149.469',
+ start: '2019-08-06T12:40:02.184Z',
+ end: '2019-08-06T20:40:02.184Z',
};
const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
const state = storeState();
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index 5570d57b8b2..e22e8cdc03d 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants';
import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
+ function secondsBetween({ start, end }) {
+ return (new Date(end) - new Date(start)) / 1000;
+ }
+
+ function minutesBetween(timeRange) {
+ return secondsBetween(timeRange) / 60;
+ }
+
+ function hoursBetween(timeRange) {
+ return minutesBetween(timeRange) / 60;
+ }
+
it('defaults to an 8 hour (28800s) difference', () => {
const params = getTimeDiff();
- expect(params.end - params.start).toEqual(28800);
+ expect(hoursBetween(params)).toEqual(8);
});
it('accepts time window as an argument', () => {
- const params = getTimeDiff(timeWindows.thirtyMinutes);
+ const params = getTimeDiff('thirtyMinutes');
- expect(params.end - params.start).not.toEqual(28800);
+ expect(minutesBetween(params)).toEqual(30);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
- nonDefaultWindows.forEach(window => {
- const params = getTimeDiff(timeWindows[window]);
- const diff = params.end - params.start;
+ nonDefaultWindows.forEach(timeWindow => {
+ const params = getTimeDiff(timeWindow);
- // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs)
- expect(diff).not.toEqual(28800);
- expect(typeof diff).toEqual('number');
+ // Ensure we're not returning the default
+ expect(hoursBetween(params)).not.toEqual(8);
});
});
});