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
This commit is contained in:
commit
c5495b526a
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,34 +1,23 @@
|
|||
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;
|
||||
|
||||
return {
|
||||
start: new Date(start * 1000).toISOString(),
|
||||
end: new Date(end * 1000).toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
export const getTimeDiff = selectedTimeWindow => {
|
||||
const end = Date.now() / 1000; // convert milliseconds to seconds
|
||||
const start = end - getTimeDifferenceSeconds(selectedTimeWindow);
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue