Commit 4816319c authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '197879-add-relative-time-ranges-to-datepicker' into 'master'

Use time ranges in date picker (utils)

See merge request gitlab-org/gitlab!23765
parents c785530c 15761a33
import dateformat from 'dateformat'; import dateformat from 'dateformat';
import { pick, omit, isEqual, isEmpty } from 'lodash';
import { secondsToMilliseconds } from './datetime_utility'; import { secondsToMilliseconds } from './datetime_utility';
const MINIMUM_DATE = new Date(0); const MINIMUM_DATE = new Date(0);
...@@ -221,3 +222,99 @@ export function getRangeType(range) { ...@@ -221,3 +222,99 @@ export function getRangeType(range) {
*/ */
export const convertToFixedRange = dateTimeRange => export const convertToFixedRange = dateTimeRange =>
handlers[getRangeType(dateTimeRange)](dateTimeRange); handlers[getRangeType(dateTimeRange)](dateTimeRange);
/**
* Returns a copy of the object only with time range
* properties relevant to time range calculation.
*
* Filtered properties are:
* - 'start'
* - 'end'
* - 'anchor'
* - 'duration'
* - 'direction': if direction is already the default, its removed.
*
* @param {Object} timeRange - A time range object
* @returns Copy of time range
*/
const pruneTimeRange = timeRange => {
const res = pick(timeRange, ['start', 'end', 'anchor', 'duration', 'direction']);
if (res.direction === DEFAULT_DIRECTION) {
return omit(res, 'direction');
}
return res;
};
/**
* Returns true if the time ranges are equal according to
* the time range calculation properties
*
* @param {Object} timeRange - A time range object
* @param {Object} other - Time range object to compare with.
* @returns true if the time ranges are equal, false otherwise
*/
export const isEqualTimeRanges = (timeRange, other) => {
const tr1 = pruneTimeRange(timeRange);
const tr2 = pruneTimeRange(other);
return isEqual(tr1, tr2);
};
/**
* Searches for a time range in a array of time ranges using
* only the properies relevant to time ranges calculation.
*
* @param {Object} timeRange - Time range to search (needle)
* @param {Array} timeRanges - Array of time tanges (haystack)
*/
export const findTimeRange = (timeRange, timeRanges) =>
timeRanges.find(element => isEqualTimeRanges(element, timeRange));
// Time Ranges as URL Parameters Utils
/**
* List of possible time ranges parameters
*/
export const timeRangeParamNames = ['start', 'end', 'anchor', 'duration_seconds', 'direction'];
/**
* Converts a valid time range to a flat key-value pairs object.
*
* Duration is flatted to avoid having nested objects.
*
* @param {Object} A time range
* @returns key-value pairs object that can be used as parameters in a URL.
*/
export const timeRangeToParams = timeRange => {
let params = pruneTimeRange(timeRange);
if (timeRange.duration) {
const durationParms = {};
Object.keys(timeRange.duration).forEach(key => {
durationParms[`duration_${key}`] = timeRange.duration[key].toString();
});
params = { ...durationParms, ...params };
params = omit(params, 'duration');
}
return params;
};
/**
* Converts a valid set of flat params to a time range object
*
* Parameters that are not part of time range object are ignored.
*
* @param {params} params - key-value pairs object.
*/
export const timeRangeFromParams = params => {
const timeRangeParams = pick(params, timeRangeParamNames);
let range = Object.entries(timeRangeParams).reduce((acc, [key, val]) => {
// unflatten duration
if (key.startsWith('duration_')) {
acc.duration = acc.duration || {};
acc.duration[key.slice('duration_'.length)] = parseInt(val, 10);
return acc;
}
return { [key]: val, ...acc };
}, {});
range = pruneTimeRange(range);
return !isEmpty(range) ? range : null;
};
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import {
timeRangeParamNames,
timeRangeFromParams,
timeRangeToParams,
} from '~/lib/utils/datetime_range';
/** /**
* 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
...@@ -93,4 +100,35 @@ export const graphDataValidatorForAnomalyValues = graphData => { ...@@ -93,4 +100,35 @@ export const graphDataValidatorForAnomalyValues = graphData => {
); );
}; };
/**
* Returns a time range from the current URL params
*
* @returns {Object} The time range defined by the
* current URL, reading from `window.location.search`
*/
export const timeRangeFromUrl = (search = window.location.search) => {
const params = queryToObject(search);
return timeRangeFromParams(params);
};
/**
* Returns a URL with no time range based on the current URL.
*
* @param {String} New URL
*/
export const removeTimeRangeParams = (url = window.location.href) =>
removeParams(timeRangeParamNames, url);
/**
* Returns a URL for the a different time range based on the
* current URL and a time range.
*
* @param {String} New URL
*/
export const timeRangeToUrl = (timeRange, url = window.location.href) => {
const toUrl = removeTimeRangeParams(url);
const params = timeRangeToParams(timeRange);
return mergeUrlParams(params, toUrl);
};
export default {}; export default {};
import * as monitoringUtils from '~/monitoring/utils'; import * as monitoringUtils from '~/monitoring/utils';
import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import { import {
mockHost,
mockProjectDir,
graphDataPrometheusQuery, graphDataPrometheusQuery,
graphDataPrometheusQueryRange, graphDataPrometheusQueryRange,
anomalyMockGraphData, anomalyMockGraphData,
} from './mock_data'; } from './mock_data';
jest.mock('~/lib/utils/url_utility');
const mockPath = `${mockHost}${mockProjectDir}/-/environments/29/metrics`;
const generatedLink = 'http://chart.link.com';
const chartTitle = 'Some metric chart';
const range = {
start: '2019-01-01T00:00:00.000Z',
end: '2019-01-10T00:00:00.000Z',
};
const rollingRange = {
duration: { seconds: 120 },
};
describe('monitoring/utils', () => { describe('monitoring/utils', () => {
const generatedLink = 'http://chart.link.com'; afterEach(() => {
const chartTitle = 'Some metric chart'; mergeUrlParams.mockReset();
queryToObject.mockReset();
});
describe('trackGenerateLinkToChartEventOptions', () => { describe('trackGenerateLinkToChartEventOptions', () => {
it('should return Cluster Monitoring options if located on Cluster Health Dashboard', () => { it('should return Cluster Monitoring options if located on Cluster Health Dashboard', () => {
...@@ -117,4 +139,75 @@ describe('monitoring/utils', () => { ...@@ -117,4 +139,75 @@ describe('monitoring/utils', () => {
expect(monitoringUtils.graphDataValidatorForAnomalyValues(fourMetrics)).toBe(false); expect(monitoringUtils.graphDataValidatorForAnomalyValues(fourMetrics)).toBe(false);
}); });
}); });
describe('timeRangeFromUrl', () => {
const { timeRangeFromUrl } = monitoringUtils;
it('returns a fixed range when query contains `start` and `end` paramters are given', () => {
queryToObject.mockReturnValueOnce(range);
expect(timeRangeFromUrl()).toEqual(range);
});
it('returns a rolling range when query contains `duration_seconds` paramters are given', () => {
const { seconds } = rollingRange.duration;
queryToObject.mockReturnValueOnce({
dashboard: '.gitlab/dashboard/my_dashboard.yml',
duration_seconds: `${seconds}`,
});
expect(timeRangeFromUrl()).toEqual(rollingRange);
});
it('returns null when no time range paramters are given', () => {
const params = {
dashboard: '.gitlab/dashboards/custom_dashboard.yml',
param1: 'value1',
param2: 'value2',
};
expect(timeRangeFromUrl(params, mockPath)).toBe(null);
});
});
describe('removeTimeRangeParams', () => {
const { removeTimeRangeParams } = monitoringUtils;
it('returns when query contains `start` and `end` paramters are given', () => {
removeParams.mockReturnValueOnce(mockPath);
expect(removeTimeRangeParams(`${mockPath}?start=${range.start}&end=${range.end}`)).toEqual(
mockPath,
);
});
});
describe('timeRangeToUrl', () => {
const { timeRangeToUrl } = monitoringUtils;
it('returns a fixed range when query contains `start` and `end` paramters are given', () => {
const toUrl = `${mockPath}?start=${range.start}&end=${range.end}`;
const fromUrl = mockPath;
removeParams.mockReturnValueOnce(fromUrl);
mergeUrlParams.mockReturnValueOnce(toUrl);
expect(timeRangeToUrl(range)).toEqual(toUrl);
expect(mergeUrlParams).toHaveBeenCalledWith(range, fromUrl);
});
it('returns a rolling range when query contains `duration_seconds` paramters are given', () => {
const { seconds } = rollingRange.duration;
const toUrl = `${mockPath}?duration_seconds=${seconds}`;
const fromUrl = mockPath;
removeParams.mockReturnValueOnce(fromUrl);
mergeUrlParams.mockReturnValueOnce(toUrl);
expect(timeRangeToUrl(rollingRange)).toEqual(toUrl);
expect(mergeUrlParams).toHaveBeenCalledWith({ duration_seconds: `${seconds}` }, fromUrl);
});
});
}); });
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