Skip to content

Commit

Permalink
Round start and end values
Browse files Browse the repository at this point in the history
When getting the start and end times, use the d3 time scale `ticks` function to round the start and end times.

Example from a query:

Before:

```json
{
          "range": {
            "@timestamp": {
              "gte": 1611262874814,
              "lte": 1611263774814,
              "format": "epoch_millis"
            }
          }
        },
```

After:

```json
{
          "range": {
            "@timestamp": {
              "gte": 1611263040000,
              "lte": 1611263880000,
              "format": "epoch_millis"
            }
          }
        },
```

The `ticks` function makes it so the amount of rounding is proportional to the size of the time range, so shorter time ranges will be rounded less.

Also fix a bug where invalid ranges in the query string were not handled correctly.

Fixes elastic#84530.
  • Loading branch information
smith committed Jan 21, 2021
1 parent 208ae0d commit a83ebd2
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,6 @@ import moment from 'moment-timezone';
import * as helpers from './helpers';

describe('url_params_context helpers', () => {
describe('getParsedDate', () => {
describe('given undefined', () => {
it('returns undefined', () => {
expect(helpers.getParsedDate(undefined)).toBeUndefined();
});
});

describe('given a parsable date', () => {
it('returns the parsed date', () => {
expect(helpers.getParsedDate('1970-01-01T00:00:00.000Z')).toEqual(
'1970-01-01T00:00:00.000Z'
);
});
});

describe('given a non-parsable date', () => {
it('returns null', () => {
expect(helpers.getParsedDate('nope')).toEqual(null);
});
});
});

describe('getDateRange', () => {
describe('when rangeFrom and rangeTo are not changed', () => {
it('returns the previous state', () => {
Expand All @@ -52,6 +30,43 @@ describe('url_params_context helpers', () => {
});
});

describe('when rangeFrom or rangeTo are falsy', () => {
it('returns the previous state', () => {
jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
expect(
helpers.getDateRange({
state: {
start: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
},
rangeFrom: '',
rangeTo: 'now',
})
).toEqual({
start: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
});
});
});

describe('when the start or end are invalid', () => {
it('returns the previous state', () => {
expect(
helpers.getDateRange({
state: {
start: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
},
rangeFrom: 'nope',
rangeTo: 'now',
})
).toEqual({
start: '1972-01-01T00:00:00.000Z',
end: '1973-01-01T00:00:00.000Z',
});
});
});

describe('when rangeFrom or rangeTo have changed', () => {
it('returns new state', () => {
jest.spyOn(datemath, 'parse').mockReturnValue(moment(0).utc());
Expand Down
29 changes: 22 additions & 7 deletions x-pack/plugins/apm/public/context/url_params_context/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { compact, pickBy } from 'lodash';
import datemath from '@elastic/datemath';
import { scaleTime } from 'd3-scale';
import { compact, pickBy } from 'lodash';
import { IUrlParams } from './types';

export function getParsedDate(rawDate?: string, opts = {}) {
function getParsedDate(rawDate?: string, options = {}) {
if (rawDate) {
const parsed = datemath.parse(rawDate, opts);
if (parsed) {
return parsed.toISOString();
const parsed = datemath.parse(rawDate, options);
if (parsed && parsed.isValid()) {
return parsed.toDate();
}
}
}
Expand All @@ -26,13 +27,27 @@ export function getDateRange({
rangeFrom?: string;
rangeTo?: string;
}) {
// If the previous state had the same range, just return that instead of calculating a new range.
if (state.rangeFrom === rangeFrom && state.rangeTo === rangeTo) {
return { start: state.start, end: state.end };
}

const start = getParsedDate(rangeFrom);
const end = getParsedDate(rangeTo, { roundUp: true });

// `getParsedDate` will return undefined for invalid or empty dates. We return
// the previous state if either date is undefined.
if (!start || !end) {
return { start: state.start, end: state.end };
}

// Calculate ticks for the time ranges to produce nicely rounded values
const ticks = scaleTime().domain([start, end]).ticks();

// Return the first and last tick values.
return {
start: getParsedDate(rangeFrom),
end: getParsedDate(rangeTo, { roundUp: true }),
start: ticks[0].toISOString(),
end: ticks[ticks.length - 1].toISOString(),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as React from 'react';
import { UrlParamsContext, UrlParamsProvider } from './url_params_context';
import { waitFor } from '@testing-library/react';
import { mount } from 'enzyme';
import { Location, History } from 'history';
import { MemoryRouter, Router } from 'react-router-dom';
import { History, Location } from 'history';
import moment from 'moment-timezone';
import * as React from 'react';
import { MemoryRouter, Router } from 'react-router-dom';
import { IUrlParams } from './types';
import { getParsedDate } from './helpers';
import { waitFor } from '@testing-library/react';
import { UrlParamsContext, UrlParamsProvider } from './url_params_context';

function mountParams(location: Location) {
return mount(
Expand Down Expand Up @@ -50,8 +49,8 @@ describe('UrlParamsContext', () => {

const wrapper = mountParams(location);
const params = getDataFromOutput(wrapper);
expect(params.start).toEqual('2010-03-15T12:00:00.000Z');
expect(params.end).toEqual('2010-04-10T12:00:00.000Z');
expect(params.start).toEqual('2010-03-17T05:00:00.000Z');
expect(params.end).toEqual('2010-04-09T05:00:00.000Z');
});

it('should update param values if location has changed', () => {
Expand All @@ -66,8 +65,8 @@ describe('UrlParamsContext', () => {
// force an update
wrapper.setProps({ abc: 123 });
const params = getDataFromOutput(wrapper);
expect(params.start).toEqual('2009-03-15T12:00:00.000Z');
expect(params.end).toEqual('2009-04-10T12:00:00.000Z');
expect(params.start).toEqual('2009-03-17T05:00:00.000Z');
expect(params.end).toEqual('2009-04-09T05:00:00.000Z');
});

it('should parse relative time ranges on mount', () => {
Expand All @@ -81,8 +80,8 @@ describe('UrlParamsContext', () => {
// force an update
wrapper.setProps({ abc: 123 });
const params = getDataFromOutput(wrapper);
expect(params.start).toEqual(getParsedDate('now-1d/d'));
expect(params.end).toEqual(getParsedDate('now-1d/d', { roundUp: true }));
expect(new Date(params.start).getTime()).not.toBeNaN();
expect(new Date(params.end).getTime()).not.toBeNaN();
});

it('should refresh the time range with new values', async () => {
Expand Down Expand Up @@ -130,8 +129,8 @@ describe('UrlParamsContext', () => {
expect(calls.length).toBe(2);

const params = getDataFromOutput(wrapper);
expect(params.start).toEqual('2005-09-20T12:00:00.000Z');
expect(params.end).toEqual('2005-10-21T12:00:00.000Z');
expect(params.start).toEqual('2005-09-21T05:00:00.000Z');
expect(params.end).toEqual('2005-10-21T05:00:00.000Z');
});

it('should refresh the time range with new values if time range is relative', async () => {
Expand Down Expand Up @@ -177,7 +176,7 @@ describe('UrlParamsContext', () => {
await waitFor(() => {});

const params = getDataFromOutput(wrapper);
expect(params.start).toEqual('2000-06-14T00:00:00.000Z');
expect(params.end).toEqual('2000-06-14T23:59:59.999Z');
expect(params.start).toEqual('2000-06-14T02:00:00.000Z');
expect(params.end).toEqual('2000-06-14T23:00:00.000Z');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ import React, {
import { withRouter } from 'react-router-dom';
import { uniqueId, mapValues } from 'lodash';
import { IUrlParams } from './types';
import { getParsedDate } from './helpers';
import { getDateRange } from './helpers';
import { resolveUrlParams } from './resolve_url_params';
import { UIFilters } from '../../../typings/ui_filters';
import {
localUIFilterNames,

// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../server/lib/ui_filters/local_ui_filters/config';
import { pickKeys } from '../../../common/utils/pick_keys';
Expand Down Expand Up @@ -79,8 +78,7 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter(
(timeRange: TimeRange) => {
refUrlParams.current = {
...refUrlParams.current,
start: getParsedDate(timeRange.rangeFrom),
end: getParsedDate(timeRange.rangeTo, { roundUp: true }),
...getDateRange({ state: {}, ...timeRange }),
};

forceUpdate(uniqueId());
Expand Down

0 comments on commit a83ebd2

Please sign in to comment.