diff options
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); }); }); }); |