Skip to content

Commit

Permalink
[7.14] [Discover/Reporting] Fix export that does not contain relative…
Browse files Browse the repository at this point in the history
… time filter (#110459) (#112090)

* [Discover/Reporting] Fix export that does not contain relative time filter (#110459)

* version 1 of fix: we set the time range on the search source at CSV generation time

* updated jest tests and updated API for getSharingData

* make time range optional for getSharingData

* pivot to updating "getTime" functionality by introducing a new flag

* update jest snapshots

* update comment

* refactored coerceToAbsoluteTime -> coerceRelativeTimeToAbsoluteTime and updated behaviour to be more specific

* fix jest test

* do not change createFilter API, rather create new createRelativeFilter API, also only use this in one place in discover

* update jest tests

* update mock

* update jest test mock

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
# Conflicts:
#	src/plugins/data/common/query/timefilter/get_time.test.ts
#	src/plugins/data/common/query/timefilter/get_time.ts

* fix types and linting

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
jloleysens and kibanamachine authored Sep 20, 2021
1 parent 635b049 commit b579c39
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 25 deletions.
89 changes: 84 additions & 5 deletions src/plugins/data/common/query/timefilter/get_time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
* Side Public License, v 1.
*/

import type { IIndexPattern } from '../..';
import type { RangeFilter } from '../../es_query/filters';
import moment from 'moment';
import sinon from 'sinon';
import { getTime, getAbsoluteTimeRange } from './get_time';
import { getTime, getRelativeTime, getAbsoluteTimeRange } from './get_time';

describe('get_time', () => {
describe('getTime', () => {
test('build range filter in iso format', () => {
const clock = sinon.useFakeTimers(moment.utc([2000, 1, 1, 0, 0, 0, 0]).valueOf());

const filter = getTime(
{
({
id: 'test',
title: 'test',
timeFieldName: 'date',
Expand All @@ -30,7 +32,7 @@ describe('get_time', () => {
filterable: true,
},
],
} as any,
} as unknown) as IIndexPattern,
{ from: 'now-60y', to: 'now' }
);
expect(filter!.range.date).toEqual({
Expand All @@ -45,7 +47,7 @@ describe('get_time', () => {
const clock = sinon.useFakeTimers(moment.utc([2000, 1, 1, 0, 0, 0, 0]).valueOf());

const filter = getTime(
{
({
id: 'test',
title: 'test',
timeFieldName: 'date',
Expand All @@ -67,7 +69,7 @@ describe('get_time', () => {
filterable: true,
},
],
} as any,
} as unknown) as IIndexPattern,
{ from: 'now-60y', to: 'now' },
{ fieldName: 'myCustomDate' }
);
Expand All @@ -79,6 +81,83 @@ describe('get_time', () => {
clock.restore();
});
});
describe('getRelativeTime', () => {
test('do not coerce relative time to absolute time when given flag', () => {
const filter = getRelativeTime(
({
id: 'test',
title: 'test',
timeFieldName: 'date',
fields: [
{
name: 'date',
type: 'date',
esTypes: ['date'],
aggregatable: true,
searchable: true,
filterable: true,
},
{
name: 'myCustomDate',
type: 'date',
esTypes: ['date'],
aggregatable: true,
searchable: true,
filterable: true,
},
],
} as unknown) as IIndexPattern,
{ from: 'now-60y', to: 'now' },
{ fieldName: 'myCustomDate' }
) as RangeFilter;

expect(filter.range.myCustomDate).toEqual({
gte: 'now-60y',
lte: 'now',
format: 'strict_date_optional_time',
});
});
test('do not coerce relative time to absolute time when given flag - with mixed from and to times', () => {
const clock = sinon.useFakeTimers(moment.utc().valueOf());
const filter = getRelativeTime(
({
id: 'test',
title: 'test',
timeFieldName: 'date',
fields: [
{
name: 'date',
type: 'date',
esTypes: ['date'],
aggregatable: true,
searchable: true,
filterable: true,
},
{
name: 'myCustomDate',
type: 'date',
esTypes: ['date'],
aggregatable: true,
searchable: true,
filterable: true,
},
],
} as unknown) as IIndexPattern,
{
from: '2020-09-01T08:30:00.000Z',
to: 'now',
},
{ fieldName: 'myCustomDate' }
) as RangeFilter;

expect(filter.range.myCustomDate).toEqual({
gte: '2020-09-01T08:30:00.000Z',
lte: 'now',
format: 'strict_date_optional_time',
});
clock.restore();
});
});
describe('getAbsoluteTimeRange', () => {
test('should forward absolute timerange as is', () => {
const from = '2000-01-01T00:00:00.000Z';
Expand Down
77 changes: 60 additions & 17 deletions src/plugins/data/common/query/timefilter/get_time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,35 @@
*/

import dateMath from '@elastic/datemath';
import { buildRangeFilter, IIndexPattern, TimeRange, TimeRangeBounds } from '../..';
import { omitBy } from 'lodash';
import type { Moment } from 'moment';
import {
buildRangeFilter,
IIndexPattern,
TimeRange,
TimeRangeBounds,
RangeFilterParams,
} from '../..';

interface CalculateBoundsOptions {
forceNow?: Date;
}

const calculateLowerBound = (from: string, forceNow?: Date): undefined | Moment =>
dateMath.parse(from, { forceNow });

const calculateUpperBound = (to: string, forceNow?: Date): undefined | Moment =>
dateMath.parse(to, { roundUp: true, forceNow });

const isRelativeTime = (value: string): boolean => value.includes('now');

export function calculateBounds(
timeRange: TimeRange,
options: CalculateBoundsOptions = {}
): TimeRangeBounds {
return {
min: dateMath.parse(timeRange.from, { forceNow: options.forceNow }),
max: dateMath.parse(timeRange.to, { roundUp: true, forceNow: options.forceNow }),
min: calculateLowerBound(timeRange.from, options.forceNow),
max: calculateUpperBound(timeRange.to, options.forceNow),
};
}

Expand All @@ -43,15 +59,31 @@ export function getTime(
indexPattern,
timeRange,
options?.fieldName || indexPattern?.timeFieldName,
options?.forceNow
options?.forceNow,
true
);
}

export function getRelativeTime(
indexPattern: IIndexPattern | undefined,
timeRange: TimeRange,
options?: { forceNow?: Date; fieldName?: string }
) {
return createTimeRangeFilter(
indexPattern,
timeRange,
options?.fieldName || indexPattern?.timeFieldName,
options?.forceNow,
false
);
}

function createTimeRangeFilter(
indexPattern: IIndexPattern | undefined,
timeRange: TimeRange,
fieldName?: string,
forceNow?: Date
forceNow?: Date,
coerceRelativeTimeToAbsoluteTime: boolean = true
) {
if (!indexPattern) {
return;
Expand All @@ -63,17 +95,28 @@ function createTimeRangeFilter(
return;
}

const bounds = calculateBounds(timeRange, { forceNow });
if (!bounds) {
return;
let rangeFilterParams: RangeFilterParams = {
format: 'strict_date_optional_time',
};

if (coerceRelativeTimeToAbsoluteTime) {
const bounds = calculateBounds(timeRange, { forceNow });
if (!bounds) {
return;
}
rangeFilterParams.gte = bounds.min?.toISOString();
rangeFilterParams.lte = bounds.max?.toISOString();
} else {
rangeFilterParams.gte = isRelativeTime(timeRange.from)
? timeRange.from
: calculateLowerBound(timeRange.from, forceNow)?.toISOString();

rangeFilterParams.lte = isRelativeTime(timeRange.to)
? timeRange.to
: calculateUpperBound(timeRange.to, forceNow)?.toISOString();
}
return buildRangeFilter(
field,
{
...(bounds.min && { gte: bounds.min.toISOString() }),
...(bounds.max && { lte: bounds.max.toISOString() }),
format: 'strict_date_optional_time',
},
indexPattern
);

rangeFilterParams = omitBy(rangeFilterParams, (v) => v == null);

return buildRangeFilter(field, rangeFilterParams, indexPattern);
}
24 changes: 23 additions & 1 deletion src/plugins/data/public/query/timefilter/timefilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
calculateBounds,
getAbsoluteTimeRange,
getTime,
getRelativeTime,
IIndexPattern,
RefreshInterval,
TimeRange,
Expand All @@ -27,7 +28,6 @@ import { createAutoRefreshLoop, AutoRefreshDoneFn } from './lib/auto_refresh_loo
export { AutoRefreshDoneFn };

// TODO: remove!

export class Timefilter {
// Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled
private enabledUpdated$ = new BehaviorSubject(false);
Expand Down Expand Up @@ -178,12 +178,34 @@ export class Timefilter {
}
};

/**
* Create a time filter that coerces all time values to absolute time.
*
* This is useful for creating a filter that ensures all ES queries will fetch the exact same data
* and leverages ES query cache for performance improvement.
*
* One use case is keeping different elements embedded in the same UI in sync.
*/
public createFilter = (indexPattern: IIndexPattern, timeRange?: TimeRange) => {
return getTime(indexPattern, timeRange ? timeRange : this._time, {
forceNow: this.nowProvider.get(),
});
};

/**
* Create a time filter that converts only absolute time to ISO strings, it leaves relative time
* values unchanged (e.g. "now-1").
*
* This is useful for sending datemath values to ES endpoints to generate reports over time.
*
* @note Consumers of this function need to ensure that the ES endpoint supports datemath.
*/
public createRelativeFilter = (indexPattern: IIndexPattern, timeRange?: TimeRange) => {
return getRelativeTime(indexPattern, timeRange ? timeRange : this._time, {
forceNow: this.nowProvider.get(),
});
};

public getBounds(): TimeRangeBounds {
return this.calculateBounds(this._time);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const createSetupContractMock = () => {
getBounds: jest.fn(),
calculateBounds: jest.fn(),
createFilter: jest.fn(),
createRelativeFilter: jest.fn(),
getRefreshIntervalDefaults: jest.fn(),
getTimeDefaults: jest.fn(),
getAbsoluteTime: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const getTopNavLinks = ({
state.appStateContainer.getState(),
services.uiSettings
);

services.share.toggleShareContextMenu({
anchorElement,
allowEmbed: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export function updateSearchSource(

// this is not the default index pattern, it determines that it's not of type rollup
if (indexPatternsUtils.isDefault(indexPattern)) {
searchSource.setField('filter', data.query.timefilter.timefilter.createFilter(indexPattern));
searchSource.setField(
'filter',
data.query.timefilter.timefilter.createRelativeFilter(indexPattern)
);
}

if (useNewFieldsApi) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export class CsvGenerator {
const index = searchSource.getField('index');

if (!index) {
throw new Error(`The search must have a revference to an index pattern!`);
throw new Error(`The search must have a reference to an index pattern!`);
}

const { maxSizeBytes, bom, escapeFormulaValues, scroll: scrollSettings } = settings;
Expand Down

0 comments on commit b579c39

Please sign in to comment.