Skip to content

Commit

Permalink
[Logs UI] Sync logs timerange with wider Kibana (#79444)
Browse files Browse the repository at this point in the history
* Sync logs timestamps with wider Kibana

Co-authored-by: Felix Stürmer <weltenwort@users.noreply.github.com>
  • Loading branch information
Kerry350 and weltenwort authored Oct 8, 2020
1 parent a8b5e9f commit 74561d8
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 6 deletions.
8 changes: 8 additions & 0 deletions src/plugins/data/public/query/timefilter/timefilter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ function clearNowTimeStub() {
delete global.nowTime;
}

test('isTimeTouched is initially set to false', () => {
expect(timefilter.isTimeTouched()).toBe(false);
});

describe('setTime', () => {
let update: sinon.SinonSpy;
let fetch: sinon.SinonSpy;
Expand Down Expand Up @@ -84,6 +88,10 @@ describe('setTime', () => {
});
});

test('should update isTimeTouched', () => {
expect(timefilter.isTimeTouched()).toBe(true);
});

test('should not add unexpected object keys to time state', () => {
const unexpectedKey = 'unexpectedKey';
timefilter.setTime({
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/data/public/query/timefilter/timefilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export class Timefilter {
private fetch$ = new Subject();

private _time: TimeRange;
// Denotes whether setTime has been called, can be used to determine if the constructor defaults are being used.
private _isTimeTouched: boolean = false;
private _refreshInterval!: RefreshInterval;
private _history: TimeHistoryContract;

Expand Down Expand Up @@ -68,6 +70,10 @@ export class Timefilter {
return this._isAutoRefreshSelectorEnabled;
}

public isTimeTouched() {
return this._isTimeTouched;
}

public getEnabledUpdated$ = () => {
return this.enabledUpdated$.asObservable();
};
Expand Down Expand Up @@ -112,6 +118,7 @@ export class Timefilter {
from: newTime.from,
to: newTime.to,
};
this._isTimeTouched = true;
this._history.add(this._time);
this.timeUpdate$.next();
this.fetch$.next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const createSetupContractMock = () => {
const timefilterMock: jest.Mocked<TimefilterContract> = {
isAutoRefreshSelectorEnabled: jest.fn(),
isTimeRangeSelectorEnabled: jest.fn(),
isTimeTouched: jest.fn(),
getEnabledUpdated$: jest.fn(),
getTimeUpdate$: jest.fn(),
getRefreshIntervalUpdate$: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import createContainer from 'constate';
import { useSetState } from 'react-use';
import { TimeKey } from '../../../../common/time';
import { datemathToEpochMillis, isValidDatemath } from '../../../utils/datemath';
import { useKibanaTimefilterTime } from '../../../hooks/use_kibana_timefilter_time';

type TimeKeyOrNull = TimeKey | null;

Expand Down Expand Up @@ -55,7 +56,6 @@ export interface LogPositionCallbacks {
updateDateRange: (newDateRage: Partial<DateRange>) => void;
}

const DEFAULT_DATE_RANGE = { startDateExpression: 'now-1d', endDateExpression: 'now' };
const DESIRED_BUFFER_PAGES = 2;

const useVisibleMidpoint = (middleKey: TimeKeyOrNull, targetPosition: TimeKeyOrNull) => {
Expand All @@ -80,7 +80,17 @@ const useVisibleMidpoint = (middleKey: TimeKeyOrNull, targetPosition: TimeKeyOrN
return store.currentValue;
};

const TIME_DEFAULTS = { from: 'now-1d', to: 'now' };

export const useLogPositionState: () => LogPositionStateParams & LogPositionCallbacks = () => {
const [getTime, setTime] = useKibanaTimefilterTime(TIME_DEFAULTS);
const { from: start, to: end } = getTime();

const DEFAULT_DATE_RANGE = {
startDateExpression: start,
endDateExpression: end,
};

// Flag to determine if `LogPositionState` has been fully initialized.
//
// When the page loads, there might be initial state in the URL. We want to
Expand Down Expand Up @@ -110,6 +120,17 @@ export const useLogPositionState: () => LogPositionStateParams & LogPositionCall
timestampsLastUpdate: Date.now(),
});

useEffect(() => {
if (isInitialized) {
if (
TIME_DEFAULTS.from !== dateRange.startDateExpression ||
TIME_DEFAULTS.to !== dateRange.endDateExpression
) {
setTime({ from: dateRange.startDateExpression, to: dateRange.endDateExpression });
}
}
}, [isInitialized, dateRange.startDateExpression, dateRange.endDateExpression, setTime]);

const { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd } = visiblePositions;

const visibleMidpoint = useVisibleMidpoint(middleKey, targetPosition);
Expand Down
43 changes: 43 additions & 0 deletions x-pack/plugins/infra/public/hooks/use_kibana_timefilter_time.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { useCallback } from 'react';
import { useUpdateEffect, useMount } from 'react-use';
import { useKibanaContextForPlugin } from './use_kibana';
import { TimeRange, TimefilterContract } from '../../../../../src/plugins/data/public';

export const useKibanaTimefilterTime = ({
from: fromDefault,
to: toDefault,
}: TimeRange): [typeof getTime, TimefilterContract['setTime']] => {
const { services } = useKibanaContextForPlugin();

const getTime = useCallback(() => {
const timefilterService = services.data.query.timefilter.timefilter;
return timefilterService.isTimeTouched()
? timefilterService.getTime()
: { from: fromDefault, to: toDefault };
}, [services.data.query.timefilter.timefilter, fromDefault, toDefault]);

return [getTime, services.data.query.timefilter.timefilter.setTime];
};

export const useSyncKibanaTimeFilterTime = (defaults: TimeRange, currentTimeRange: TimeRange) => {
const [, setTime] = useKibanaTimefilterTime(defaults);

// On first mount we only want to sync time with Kibana if the derived currentTimeRange (e.g. from URL params)
// differs from our defaults.
useMount(() => {
if (defaults.from !== currentTimeRange.from || defaults.to !== currentTimeRange.to) {
setTime({ from: currentTimeRange.from, to: currentTimeRange.to });
}
});

// Sync explicit changes *after* mount back to Kibana
useUpdateEffect(() => {
setTime({ from: currentTimeRange.from, to: currentTimeRange.to });
}, [currentTimeRange.from, currentTimeRange.to, setTime]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { fold } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';

import { useUrlState } from '../../../utils/use_url_state';
import {
useKibanaTimefilterTime,
useSyncKibanaTimeFilterTime,
} from '../../../hooks/use_kibana_timefilter_time';

const autoRefreshRT = rt.union([
rt.type({
Expand All @@ -29,12 +32,16 @@ const urlTimeRangeRT = rt.union([stringTimeRangeRT, rt.undefined]);

const TIME_RANGE_URL_STATE_KEY = 'timeRange';
const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh';
const TIME_DEFAULTS = { from: 'now-2w', to: 'now' };

export const useLogEntryCategoriesResultsUrlState = () => {
const [getTime] = useKibanaTimefilterTime(TIME_DEFAULTS);
const { from: start, to: end } = getTime();

const [timeRange, setTimeRange] = useUrlState({
defaultState: {
startTime: 'now-2w',
endTime: 'now',
startTime: start,
endTime: end,
},
decodeUrlState: (value: unknown) =>
pipe(urlTimeRangeRT.decode(value), fold(constant(undefined), identity)),
Expand All @@ -43,6 +50,8 @@ export const useLogEntryCategoriesResultsUrlState = () => {
writeDefaultState: true,
});

useSyncKibanaTimeFilterTime(TIME_DEFAULTS, { from: timeRange.startTime, to: timeRange.endTime });

const [autoRefresh, setAutoRefresh] = useUrlState({
defaultState: {
isPaused: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';

import { useUrlState } from '../../../utils/use_url_state';
import {
useKibanaTimefilterTime,
useSyncKibanaTimeFilterTime,
} from '../../../hooks/use_kibana_timefilter_time';

const autoRefreshRT = rt.union([
rt.type({
Expand All @@ -29,12 +33,16 @@ const urlTimeRangeRT = rt.union([stringTimeRangeRT, rt.undefined]);

const TIME_RANGE_URL_STATE_KEY = 'timeRange';
const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh';
const TIME_DEFAULTS = { from: 'now-2w', to: 'now' };

export const useLogAnalysisResultsUrlState = () => {
const [getTime] = useKibanaTimefilterTime(TIME_DEFAULTS);
const { from: start, to: end } = getTime();

const [timeRange, setTimeRange] = useUrlState({
defaultState: {
startTime: 'now-2w',
endTime: 'now',
startTime: start,
endTime: end,
},
decodeUrlState: (value: unknown) =>
pipe(urlTimeRangeRT.decode(value), fold(constant(undefined), identity)),
Expand All @@ -43,6 +51,8 @@ export const useLogAnalysisResultsUrlState = () => {
writeDefaultState: true,
});

useSyncKibanaTimeFilterTime(TIME_DEFAULTS, { from: timeRange.startTime, to: timeRange.endTime });

const [autoRefresh, setAutoRefresh] = useUrlState({
defaultState: {
isPaused: false,
Expand Down

0 comments on commit 74561d8

Please sign in to comment.