Commit c5495b52 authored by Mike Greiling's avatar Mike Greiling

Merge branch '62973-specify-time-frame-in-shareable-link-for-embedding-metrics' into 'master'

Specify time frame in metrics page

See merge request gitlab-org/gitlab-ce!31283
parents eec1ed52 055a7b97
...@@ -2,8 +2,8 @@ import { join as joinPaths } from 'path'; ...@@ -2,8 +2,8 @@ import { join as joinPaths } from 'path';
// Returns an array containing the value(s) of the // Returns an array containing the value(s) of the
// of the key passed as an argument // of the key passed as an argument
export function getParameterValues(sParam) { export function getParameterValues(sParam, url = window.location) {
const sPageURL = decodeURIComponent(window.location.search.substring(1)); const sPageURL = decodeURIComponent(new URL(url).search.substring(1));
return sPageURL.split('&').reduce((acc, urlParam) => { return sPageURL.split('&').reduce((acc, urlParam) => {
const sParameterName = urlParam.split('='); const sParameterName = urlParam.split('=');
......
...@@ -18,8 +18,8 @@ import MonitorSingleStatChart from './charts/single_stat.vue'; ...@@ -18,8 +18,8 @@ import MonitorSingleStatChart from './charts/single_stat.vue';
import PanelType from './panel_type.vue'; import PanelType from './panel_type.vue';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import { sidebarAnimationDuration, timeWindows, timeWindowsKeyNames } from '../constants'; import { sidebarAnimationDuration, timeWindows } from '../constants';
import { getTimeDiff } from '../utils'; import { getTimeDiff, getTimeWindow } from '../utils';
let sidebarMutationObserver; let sidebarMutationObserver;
...@@ -147,6 +147,7 @@ export default { ...@@ -147,6 +147,7 @@ export default {
selectedTimeWindow: '', selectedTimeWindow: '',
selectedTimeWindowKey: '', selectedTimeWindowKey: '',
formIsValid: null, formIsValid: null,
timeWindows: {},
}; };
}, },
computed: { computed: {
...@@ -184,17 +185,6 @@ export default { ...@@ -184,17 +185,6 @@ export default {
currentDashboard: this.currentDashboard, currentDashboard: this.currentDashboard,
projectPath: this.projectPath, 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() { beforeDestroy() {
if (sidebarMutationObserver) { if (sidebarMutationObserver) {
...@@ -205,7 +195,20 @@ export default { ...@@ -205,7 +195,20 @@ export default {
if (!this.hasMetrics) { if (!this.hasMetrics) {
this.setGettingStartedEmptyState(); this.setGettingStartedEmptyState();
} else { } 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 = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), { sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
...@@ -259,7 +262,8 @@ export default { ...@@ -259,7 +262,8 @@ export default {
return this.timeWindows[key] === this.selectedTimeWindow; return this.timeWindows[key] === this.selectedTimeWindow;
}, },
setTimeWindowParameter(key) { setTimeWindowParameter(key) {
return `?time_window=${key}`; const { start, end } = getTimeDiff(key);
return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
}, },
groupHasData(group) { groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0; return this.chartsWithData(group.metrics).length > 0;
......
<script> <script>
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import MonitorAreaChart from './charts/area.vue'; import MonitorAreaChart from './charts/area.vue';
import { sidebarAnimationDuration, timeWindowsKeyNames, timeWindows } from '../constants'; import { sidebarAnimationDuration } from '../constants';
import { getTimeDiff } from '../utils'; import { getTimeDiff } from '../utils';
let sidebarMutationObserver; let sidebarMutationObserver;
...@@ -19,10 +20,17 @@ export default { ...@@ -19,10 +20,17 @@ export default {
}, },
}, },
data() { 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 { return {
params: { params,
...getTimeDiff(timeWindows[timeWindowsKeyNames.eightHours]),
},
elWidth: 0, elWidth: 0,
}; };
}, },
...@@ -73,7 +81,7 @@ export default { ...@@ -73,7 +81,7 @@ export default {
prometheusEndpointEnabled: true, prometheusEndpointEnabled: true,
}); });
this.setEndpoints({ this.setEndpoints({
dashboardEndpoint: this.dashboardUrl, dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl),
}); });
this.setShowErrorBanner(false); this.setShowErrorBanner(false);
}, },
......
...@@ -21,11 +21,19 @@ export const timeWindows = { ...@@ -21,11 +21,19 @@ export const timeWindows = {
oneWeek: __('1 week'), oneWeek: __('1 week'),
}; };
export const timeWindowsKeyNames = { export const secondsIn = {
thirtyMinutes: 'thirtyMinutes', thirtyMinutes: 60 * 30,
threeHours: 'threeHours', threeHours: 60 * 60 * 3,
eightHours: 'eightHours', eightHours: 60 * 60 * 8,
oneDay: 'oneDay', oneDay: 60 * 60 * 24 * 1,
threeDays: 'threeDays', threeDays: 60 * 60 * 24 * 3,
oneWeek: 'oneWeek', oneWeek: 60 * 60 * 24 * 7 * 1,
}; };
export const timeWindowsKeyNames = Object.keys(secondsIn).reduce(
(otherTimeWindows, timeWindow) => ({
...otherTimeWindows,
[timeWindow]: timeWindow,
}),
{},
);
...@@ -151,7 +151,7 @@ function fetchPrometheusResult(prometheusEndpoint, params) { ...@@ -151,7 +151,7 @@ function fetchPrometheusResult(prometheusEndpoint, params) {
*/ */
export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
const { start, end } = params; const { start, end } = params;
const timeDiff = end - start; const timeDiff = (new Date(end) - new Date(start)) / 1000;
const minStep = 60; const minStep = 60;
const queryDataPoints = 600; const queryDataPoints = 600;
......
import { timeWindows } from './constants'; import { secondsIn, timeWindowsKeyNames } from './constants';
/** export const getTimeDiff = timeWindow => {
* method that converts a predetermined time window to minutes const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
* defaults to 8 hours as the default option const difference = secondsIn[timeWindow] || secondsIn.eightHours;
* @param {String} timeWindow - The time window to convert to minutes const start = end - difference;
* @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 = selectedTimeWindow => { return {
const end = Date.now() / 1000; // convert milliseconds to seconds start: new Date(start * 1000).toISOString(),
const start = end - getTimeDifferenceSeconds(selectedTimeWindow); end: new Date(end * 1000).toISOString(),
};
return { start, end };
}; };
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 * 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 * that needs a time series as a response from a prometheus query (query_range) is
......
---
title: Allow links to metrics dashboard at a specific time
merge_request: 31283
author:
type: added
...@@ -34,6 +34,41 @@ describe('URL utility', () => { ...@@ -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', () => { describe('mergeUrlParams', () => {
it('adds w', () => { it('adds w', () => {
expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag'); expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
......
...@@ -307,7 +307,7 @@ describe('Dashboard', () => { ...@@ -307,7 +307,7 @@ describe('Dashboard', () => {
}); });
spyOn(component.$store, 'dispatch').and.stub(); spyOn(component.$store, 'dispatch').and.stub();
const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough();
component.$store.commit( component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
...@@ -319,7 +319,7 @@ describe('Dashboard', () => { ...@@ -319,7 +319,7 @@ describe('Dashboard', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(component.$store.dispatch).toHaveBeenCalled(); expect(component.$store.dispatch).toHaveBeenCalled();
expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow); expect(getTimeDiffSpy).toHaveBeenCalled();
done(); done();
}) })
...@@ -327,7 +327,17 @@ describe('Dashboard', () => { ...@@ -327,7 +327,17 @@ describe('Dashboard', () => {
}); });
it('shows a specific time window selected from the url params', done => { 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({ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
......
...@@ -313,8 +313,8 @@ describe('Monitoring store actions', () => { ...@@ -313,8 +313,8 @@ describe('Monitoring store actions', () => {
it('commits prometheus query result', done => { it('commits prometheus query result', done => {
const commit = jasmine.createSpy(); const commit = jasmine.createSpy();
const params = { const params = {
start: '1557216349.469', start: '2019-08-06T12:40:02.184Z',
end: '1557218149.469', end: '2019-08-06T20:40:02.184Z',
}; };
const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0]; const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
const state = storeState(); const state = storeState();
......
...@@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants'; ...@@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants';
import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data'; import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => { 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', () => { it('defaults to an 8 hour (28800s) difference', () => {
const params = getTimeDiff(); const params = getTimeDiff();
expect(params.end - params.start).toEqual(28800); expect(hoursBetween(params)).toEqual(8);
}); });
it('accepts time window as an argument', () => { 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', () => { it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
nonDefaultWindows.forEach(window => { nonDefaultWindows.forEach(timeWindow => {
const params = getTimeDiff(timeWindows[window]); const params = getTimeDiff(timeWindow);
const diff = params.end - params.start;
// Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs) // Ensure we're not returning the default
expect(diff).not.toEqual(28800); expect(hoursBetween(params)).not.toEqual(8);
expect(typeof diff).toEqual('number');
}); });
}); });
}); });
......
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