Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Search] Search Sessions with relative time range #84405

Merged
merged 13 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface DataPublicPluginStart
| [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | <code>AutocompleteStart</code> | autocomplete service [AutocompleteStart](./kibana-plugin-plugins-data-public.autocompletestart.md) |
| [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | <code>FieldFormatsStart</code> | field formats service [FieldFormatsStart](./kibana-plugin-plugins-data-public.fieldformatsstart.md) |
| [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | <code>IndexPatternsContract</code> | index patterns service [IndexPatternsContract](./kibana-plugin-plugins-data-public.indexpatternscontract.md) |
| [nowProvider](./kibana-plugin-plugins-data-public.datapublicpluginstart.nowprovider.md) | <code>NowProviderPublicContract</code> | |
| [query](./kibana-plugin-plugins-data-public.datapublicpluginstart.query.md) | <code>QueryStart</code> | query service [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) |
| [search](./kibana-plugin-plugins-data-public.datapublicpluginstart.search.md) | <code>ISearchStart</code> | search service [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) |
| [ui](./kibana-plugin-plugins-data-public.datapublicpluginstart.ui.md) | <code>DataPublicPluginStartUi</code> | prewired UI components [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) &gt; [nowProvider](./kibana-plugin-plugins-data-public.datapublicpluginstart.nowprovider.md)

## DataPublicPluginStart.nowProvider property

<b>Signature:</b>

```typescript
nowProvider: NowProviderPublicContract;
```
6 changes: 5 additions & 1 deletion src/plugins/dashboard/public/application/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,14 @@ export function DashboardApp({
).subscribe(() => refreshDashboardContainer())
);
subscriptions.add(
data.search.session.onRefresh$.subscribe(() => {
merge(
data.search.session.onRefresh$,
data.query.timefilter.timefilter.getAutoRefreshFetch$()
).subscribe(() => {
setLastReloadTime(() => new Date().getTime());
})
);

dashboardStateManager.registerChangeListener(() => {
// we aren't checking dirty state because there are changes the container needs to know about
// that won't make the dashboard "dirty" - like a view mode change.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,22 @@ function getUrlGeneratorState({
data,
getAppState,
getDashboardId,
forceAbsoluteTime, // TODO: not implemented
forceAbsoluteTime,
}: {
data: DataPublicPluginStart;
getAppState: () => DashboardAppState;
getDashboardId: () => string;
/**
* Can force time range from time filter to convert from relative to absolute time range
*/
forceAbsoluteTime: boolean;
}): DashboardUrlGeneratorState {
const appState = getAppState();
return {
dashboardId: getDashboardId(),
timeRange: data.query.timefilter.timefilter.getTime(),
timeRange: forceAbsoluteTime
? data.query.timefilter.timefilter.getAbsoluteTime()
: data.query.timefilter.timefilter.getTime(),
filters: data.query.filterManager.getFilters(),
query: data.query.queryString.formatQuery(appState.query),
savedQuery: appState.savedQuery,
Expand Down
17 changes: 16 additions & 1 deletion src/plugins/data/common/query/timefilter/get_time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import moment from 'moment';
import sinon from 'sinon';
import { getTime } from './get_time';
import { getTime, getAbsoluteTimeRange } from './get_time';

describe('get_time', () => {
describe('getTime', () => {
Expand Down Expand Up @@ -90,4 +90,19 @@ describe('get_time', () => {
clock.restore();
});
});
describe('getAbsoluteTimeRange', () => {
test('should forward absolute timerange as is', () => {
const from = '2000-01-01T00:00:00.000Z';
const to = '2000-02-01T00:00:00.000Z';
expect(getAbsoluteTimeRange({ from, to })).toEqual({ from, to });
});

test('should convert relative to absolute', () => {
const clock = sinon.useFakeTimers(moment.utc([2000, 1, 0, 0, 0, 0, 0]).valueOf());
const from = '2000-01-01T00:00:00.000Z';
const to = moment.utc(clock.now).toISOString();
expect(getAbsoluteTimeRange({ from, to: 'now' })).toEqual({ from, to });
clock.restore();
});
});
});
11 changes: 11 additions & 0 deletions src/plugins/data/common/query/timefilter/get_time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ export function calculateBounds(
};
}

export function getAbsoluteTimeRange(
Dosant marked this conversation as resolved.
Show resolved Hide resolved
timeRange: TimeRange,
{ forceNow }: { forceNow?: Date } = {}
): TimeRange {
const { min, max } = calculateBounds(timeRange, { forceNow });
return {
from: min ? min.toISOString() : timeRange.from,
to: max ? max.toISOString() : timeRange.to,
};
}

export function getTime(
indexPattern: IIndexPattern | undefined,
timeRange: TimeRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface EsaggsStartDependencies {
deserializeFieldFormat: FormatFactory;
indexPatterns: IndexPatternsContract;
searchSource: ISearchStartSearchSource;
getNow?: () => Date;
}

/** @internal */
Expand Down Expand Up @@ -118,7 +119,8 @@ export async function handleEsaggsRequest(
args: Arguments,
params: RequestHandlerParams
): Promise<Datatable> {
const resolvedTimeRange = input?.timeRange && calculateBounds(input.timeRange);
const resolvedTimeRange =
Dosant marked this conversation as resolved.
Show resolved Hide resolved
input?.timeRange && calculateBounds(input.timeRange, { forceNow: params.getNow?.() });

const response = await handleRequest(params);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface RequestHandlerParams {
searchSourceService: ISearchStartSearchSource;
timeFields?: string[];
timeRange?: TimeRange;
getNow?: () => Date;
}

export const handleRequest = async ({
Expand All @@ -67,7 +68,9 @@ export const handleRequest = async ({
searchSourceService,
timeFields,
timeRange,
getNow,
}: RequestHandlerParams) => {
const forceNow = getNow?.();
const searchSource = await searchSourceService.create();

searchSource.setField('index', indexPattern);
Expand Down Expand Up @@ -115,7 +118,7 @@ export const handleRequest = async ({
if (timeRange && allTimeFields.length > 0) {
timeFilterSearchSource.setField('filter', () => {
return allTimeFields
.map((fieldName) => getTime(indexPattern, timeRange, { fieldName }))
.map((fieldName) => getTime(indexPattern, timeRange, { fieldName, forceNow }))
.filter(isRangeFilter);
});
}
Expand Down Expand Up @@ -183,7 +186,7 @@ export const handleRequest = async ({
}
}

const parsedTimeRange = timeRange ? calculateBounds(timeRange) : null;
const parsedTimeRange = timeRange ? calculateBounds(timeRange, { forceNow }) : null;
const tabifyParams = {
metricsAtAllLevels,
partialRows,
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/data/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { fieldFormatsServiceMock } from './field_formats/mocks';
import { searchServiceMock } from './search/mocks';
import { queryServiceMock } from './query/mocks';
import { AutocompleteStart, AutocompleteSetup } from './autocomplete';
import { createNowProviderMock } from './now_provider/mocks';

export type Setup = jest.Mocked<ReturnType<Plugin['setup']>>;
export type Start = jest.Mocked<ReturnType<Plugin['start']>>;
Expand Down Expand Up @@ -76,6 +77,7 @@ const createStartContract = (): Start => {
get: jest.fn().mockReturnValue(Promise.resolve({})),
clearCache: jest.fn(),
} as unknown) as IndexPatternsContract,
nowProvider: createNowProviderMock(),
};
};

Expand Down
24 changes: 24 additions & 0 deletions src/plugins/data/public/now_provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export {
NowProvider,
NowProviderInternalContract,
NowProviderPublicContract,
} from './now_provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { getForceNowFromUrl } from './get_force_now_from_url';
const originalLocation = window.location;
afterAll(() => {
window.location = originalLocation;
});

function mockLocation(url: string) {
// @ts-ignore
delete window.location;
// @ts-ignore
window.location = new URL(url);
}

test('should get force now from URL', () => {
const dateString = '1999-01-01T00:00:00.000Z';
mockLocation(`https://elastic.co/?forceNow=${dateString}`);

expect(getForceNowFromUrl()).toEqual(new Date(dateString));
});

test('should throw if force now is invalid', () => {
const dateString = 'invalid-date';
mockLocation(`https://elastic.co/?forceNow=${dateString}`);

expect(() => getForceNowFromUrl()).toThrowError();
});

test('should return undefined if no forceNow', () => {
mockLocation(`https://elastic.co/`);
expect(getForceNowFromUrl()).toBe(undefined);
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/

import { parse } from 'query-string';

/** @internal */
export function parseQueryString() {
export function getForceNowFromUrl(): Date | undefined {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this moved from timefilter to this new nowProvider
Long term we should make apps responsible for getting this from the URL and setting it into nowProvider

const forceNow = parseQueryString().forceNow as string;
if (!forceNow) {
return;
}

const ts = Date.parse(forceNow);
if (isNaN(ts)) {
throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`);
}
return new Date(ts);
}

/** @internal */
function parseQueryString() {
lizozom marked this conversation as resolved.
Show resolved Hide resolved
// window.location.search is an empty string
// get search from href
const hrefSplit = window.location.href.split('?');
Expand Down
20 changes: 20 additions & 0 deletions src/plugins/data/public/now_provider/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

export { getForceNowFromUrl } from './get_force_now_from_url';
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,12 @@
* under the License.
*/

import { parseQueryString } from './parse_querystring';
import { NowProviderInternalContract } from './now_provider';

/** @internal */
export function getForceNow() {
const forceNow = parseQueryString().forceNow as string;
if (!forceNow) {
return;
}

const ticks = Date.parse(forceNow);
if (isNaN(ticks)) {
throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`);
}
return new Date(ticks);
}
export const createNowProviderMock = (): jest.Mocked<NowProviderInternalContract> => {
return {
get: jest.fn(() => new Date()),
set: jest.fn(),
reset: jest.fn(),
};
};
55 changes: 55 additions & 0 deletions src/plugins/data/public/now_provider/now_provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { NowProvider, NowProviderInternalContract } from './now_provider';

let mockDateFromUrl: undefined | Date;
let nowProvider: NowProviderInternalContract;

jest.mock('./lib', () => ({
// @ts-ignore
...jest.requireActual('./lib'),
getForceNowFromUrl: () => mockDateFromUrl,
}));

beforeEach(() => {
nowProvider = new NowProvider();
});
afterEach(() => {
mockDateFromUrl = undefined;
});

test('should return Date.now() by default', async () => {
const now = Date.now();
await new Promise((r) => setTimeout(r, 10));
expect(nowProvider.get().getTime()).toBeGreaterThan(now);
});

test('should forceNow from URL', async () => {
mockDateFromUrl = new Date('1999-01-01T00:00:00.000Z');
nowProvider = new NowProvider();
expect(nowProvider.get()).toEqual(mockDateFromUrl);
});

test('should forceNow from URL if custom now is set', async () => {
mockDateFromUrl = new Date('1999-01-01T00:00:00.000Z');
nowProvider = new NowProvider();
nowProvider.set(new Date());
expect(nowProvider.get()).toEqual(mockDateFromUrl);
});
Loading