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';
// 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('=');
......
......@@ -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;
......
<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);
},
......
......@@ -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,
}),
{},
);
......@@ -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;
......
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
......
---
title: Allow links to metrics dashboard at a specific time
merge_request: 31283
author:
type: added
......@@ -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');
......
......@@ -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'),
......
......@@ -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();
......
......@@ -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);
});
});
});
......
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