mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Move useDateRange into data plugin (#212548)
As discussed offline, the existing `useDateRange` hook integrating the timefilter contract with the react lifecycle is a nice abstraction that makes sense to be part of the data plugin directly. This PR moves it into the timefilterclass: ``` const { timeRange, absoluteTimeRange, setTimeRange } = data.query.timefilter.timefilter.useTimefilter(); ``` All consumers have been changed to use this directly.
This commit is contained in:
parent
300e35012d
commit
8d7f34e2b0
12 changed files with 194 additions and 140 deletions
|
@ -25,6 +25,7 @@ import {
|
|||
} from '../../../common';
|
||||
import { TimeHistoryContract } from './time_history';
|
||||
import { createAutoRefreshLoop, AutoRefreshDoneFn } from './lib/auto_refresh_loop';
|
||||
import { TimefilterHook, createUseTimefilterHook } from './use_timefilter';
|
||||
|
||||
export type { AutoRefreshDoneFn };
|
||||
|
||||
|
@ -52,6 +53,8 @@ export class Timefilter {
|
|||
private readonly timeDefaults: TimeRange;
|
||||
private readonly refreshIntervalDefaults: RefreshInterval;
|
||||
|
||||
public readonly useTimefilter: () => TimefilterHook;
|
||||
|
||||
// Used when an auto refresh is triggered
|
||||
private readonly autoRefreshLoop = createAutoRefreshLoop();
|
||||
|
||||
|
@ -73,6 +76,8 @@ export class Timefilter {
|
|||
this._minRefreshInterval = config.minRefreshIntervalDefault;
|
||||
this._time = config.timeDefaults;
|
||||
this.setRefreshInterval(config.refreshIntervalDefaults);
|
||||
|
||||
this.useTimefilter = createUseTimefilterHook(this);
|
||||
}
|
||||
|
||||
public isTimeRangeSelectorEnabled() {
|
||||
|
|
|
@ -42,6 +42,7 @@ const createSetupContractMock = () => {
|
|||
getRefreshIntervalDefaults: jest.fn(),
|
||||
getTimeDefaults: jest.fn(),
|
||||
getAbsoluteTime: jest.fn(),
|
||||
useTimefilter: jest.fn(),
|
||||
};
|
||||
|
||||
const historyMock: jest.Mocked<TimeHistoryContract> = {
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { Subject } from 'rxjs';
|
||||
import { createUseTimefilterHook } from './use_timefilter';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
|
||||
describe('useTimefilter hook', () => {
|
||||
const timeUpdateSubject = new Subject<void>();
|
||||
|
||||
const initialTimeRange: TimeRange = {
|
||||
from: 'now-1d',
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
const initialAbsoluteTimeRange = {
|
||||
from: '2020-01-01T00:00:00Z',
|
||||
to: '2020-01-02T00:00:00Z',
|
||||
};
|
||||
|
||||
// Track the current time range in the mock
|
||||
let currentTimeRange = { ...initialTimeRange };
|
||||
|
||||
const mockTimefilter = {
|
||||
getTime: jest.fn().mockImplementation(() => currentTimeRange),
|
||||
getAbsoluteTime: jest.fn().mockReturnValue(initialAbsoluteTimeRange),
|
||||
setTime: jest.fn().mockImplementation((timeRange) => {
|
||||
currentTimeRange = timeRange;
|
||||
}),
|
||||
getTimeUpdate$: jest.fn().mockReturnValue(timeUpdateSubject),
|
||||
};
|
||||
|
||||
const useTimefilter = createUseTimefilterHook(mockTimefilter as any);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Reset the current time range for each test
|
||||
currentTimeRange = { ...initialTimeRange };
|
||||
});
|
||||
|
||||
test('time range and absolute time range should have the right values', () => {
|
||||
const { result } = renderHook(() => useTimefilter());
|
||||
|
||||
expect(result.current.timeRange).toEqual(initialTimeRange);
|
||||
expect(result.current.absoluteTimeRange).toEqual({
|
||||
start: new Date(initialAbsoluteTimeRange.from).getTime(),
|
||||
end: new Date(initialAbsoluteTimeRange.to).getTime(),
|
||||
});
|
||||
});
|
||||
|
||||
test('hook should rerender when the time update gets updated', () => {
|
||||
const { result } = renderHook(() => useTimefilter());
|
||||
|
||||
const updatedTimeRange = {
|
||||
from: '2021-01-01T00:00:00Z',
|
||||
to: '2021-01-02T00:00:00Z',
|
||||
};
|
||||
|
||||
const updatedAbsoluteTimeRange = {
|
||||
from: '2021-01-01T00:00:00Z',
|
||||
to: '2021-01-02T00:00:00Z',
|
||||
};
|
||||
|
||||
// Update the current time range for the mock
|
||||
currentTimeRange = updatedTimeRange;
|
||||
mockTimefilter.getAbsoluteTime.mockReturnValue(updatedAbsoluteTimeRange);
|
||||
|
||||
act(() => {
|
||||
timeUpdateSubject.next();
|
||||
});
|
||||
|
||||
expect(result.current.timeRange).toEqual(updatedTimeRange);
|
||||
expect(result.current.absoluteTimeRange).toEqual({
|
||||
start: new Date(updatedAbsoluteTimeRange.from).getTime(),
|
||||
end: new Date(updatedAbsoluteTimeRange.to).getTime(),
|
||||
});
|
||||
});
|
||||
|
||||
test('setTimeRange should call through correctly', () => {
|
||||
const { result } = renderHook(() => useTimefilter());
|
||||
|
||||
const newTimeRange = {
|
||||
from: '2022-01-01T00:00:00Z',
|
||||
to: '2022-01-02T00:00:00Z',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.setTimeRange(newTimeRange);
|
||||
});
|
||||
|
||||
expect(mockTimefilter.setTime).toHaveBeenCalledWith(newTimeRange);
|
||||
|
||||
// Test with callback form
|
||||
act(() => {
|
||||
result.current.setTimeRange((prevRange) => ({
|
||||
...prevRange,
|
||||
from: '2022-02-01T00:00:00Z',
|
||||
}));
|
||||
});
|
||||
|
||||
expect(mockTimefilter.setTime).toHaveBeenCalledWith({
|
||||
...newTimeRange,
|
||||
from: '2022-02-01T00:00:00Z',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { Timefilter } from './timefilter';
|
||||
|
||||
export interface TimefilterHook {
|
||||
timeRange: TimeRange;
|
||||
absoluteTimeRange: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
setTimeRange: React.Dispatch<React.SetStateAction<TimeRange>>;
|
||||
}
|
||||
|
||||
export function createUseTimefilterHook(timefilter: Timefilter) {
|
||||
return function useTimefilter(): TimefilterHook {
|
||||
const [timeRange, setTimeRange] = useState(() => timefilter.getTime());
|
||||
|
||||
const [absoluteTimeRange, setAbsoluteTimeRange] = useState(() => timefilter.getAbsoluteTime());
|
||||
|
||||
useEffect(() => {
|
||||
const timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe({
|
||||
next: () => {
|
||||
setTimeRange(() => timefilter.getTime());
|
||||
setAbsoluteTimeRange(() => timefilter.getAbsoluteTime());
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
timeUpdateSubscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const setTimeRangeMemoized: React.Dispatch<React.SetStateAction<TimeRange>> = useCallback(
|
||||
(nextOrCallback) => {
|
||||
const val =
|
||||
typeof nextOrCallback === 'function'
|
||||
? nextOrCallback(timefilter.getTime())
|
||||
: nextOrCallback;
|
||||
|
||||
timefilter.setTime(val);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const asEpoch = useMemo(() => {
|
||||
return {
|
||||
start: new Date(absoluteTimeRange.from).getTime(),
|
||||
end: new Date(absoluteTimeRange.to).getTime(),
|
||||
};
|
||||
}, [absoluteTimeRange]);
|
||||
|
||||
return {
|
||||
timeRange,
|
||||
absoluteTimeRange: asEpoch,
|
||||
setTimeRange: setTimeRangeMemoized,
|
||||
};
|
||||
};
|
||||
}
|
|
@ -24,7 +24,6 @@ import { useStreamsAppFetch } from '../../../../hooks/use_streams_app_fetch';
|
|||
import { useKibana } from '../../../../hooks/use_kibana';
|
||||
import { DetectedField, ProcessorDefinitionWithUIAttributes } from '../types';
|
||||
import { processorConverter } from '../utils';
|
||||
import { useDateRange } from '../../../../hooks/use_date_range';
|
||||
|
||||
export type Simulation = APIReturnType<'POST /api/streams/{name}/processing/_simulate'>;
|
||||
export type ProcessorMetrics =
|
||||
|
@ -95,7 +94,7 @@ export const useProcessingSimulator = ({
|
|||
|
||||
const {
|
||||
absoluteTimeRange: { start, end },
|
||||
} = useDateRange({ data });
|
||||
} = data.query.timefilter.timefilter.useTimefilter();
|
||||
|
||||
const draftProcessors = useMemo(
|
||||
() => processors.filter((processor) => processor.status === 'draft'),
|
||||
|
|
|
@ -29,7 +29,6 @@ import {
|
|||
docsFilterOptions,
|
||||
} from './hooks/use_processing_simulator';
|
||||
import { AssetImage } from '../../asset_image';
|
||||
import { useDateRange } from '../../../hooks/use_date_range';
|
||||
|
||||
interface ProcessorOutcomePreviewProps {
|
||||
columns: TableColumn[];
|
||||
|
@ -53,7 +52,7 @@ export const ProcessorOutcomePreview = ({
|
|||
const { dependencies } = useKibana();
|
||||
const { data } = dependencies.start;
|
||||
|
||||
const { timeRange, setTimeRange } = useDateRange({ data });
|
||||
const { timeRange, setTimeRange } = data.query.timefilter.timefilter.useTimefilter();
|
||||
|
||||
const tableColumns = useMemo(() => {
|
||||
switch (selectedDocsFilter) {
|
||||
|
|
|
@ -33,7 +33,6 @@ import { useKibana } from '../../../hooks/use_kibana';
|
|||
import { DataStreamStats } from './hooks/use_data_stream_stats';
|
||||
import { formatBytes } from './helpers/format_bytes';
|
||||
import { StreamsAppSearchBar } from '../../streams_app_search_bar';
|
||||
import { useDateRange } from '../../../hooks/use_date_range';
|
||||
import { useIngestionRate, useIngestionRatePerTier } from './hooks/use_ingestion_rate';
|
||||
import { useIlmPhasesColorAndDescription } from './hooks/use_ilm_phases_color_and_description';
|
||||
|
||||
|
@ -53,7 +52,7 @@ export function IngestionRate({
|
|||
start: { data },
|
||||
},
|
||||
} = useKibana();
|
||||
const { timeRange, setTimeRange } = useDateRange({ data });
|
||||
const { timeRange, setTimeRange } = data.query.timefilter.timefilter.useTimefilter();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -24,7 +24,6 @@ import { StreamsAppSearchBar } from '../../streams_app_search_bar';
|
|||
import { useRoutingState } from './hooks/routing_state';
|
||||
import { PreviewPanelIllustration } from './preview_panel_illustration';
|
||||
import { PreviewMatches } from './preview_matches';
|
||||
import { useDateRange } from '../../../hooks/use_date_range';
|
||||
|
||||
export function PreviewPanel({
|
||||
definition,
|
||||
|
@ -41,9 +40,9 @@ export function PreviewPanel({
|
|||
|
||||
const {
|
||||
timeRange,
|
||||
absoluteTimeRange: { start, end },
|
||||
setTimeRange,
|
||||
} = useDateRange({ data });
|
||||
absoluteTimeRange: { start, end },
|
||||
} = data.query.timefilter.timefilter.useTimefilter();
|
||||
|
||||
const {
|
||||
isLoadingDocuments,
|
||||
|
|
|
@ -18,7 +18,6 @@ import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
|
|||
import { DashboardLocatorParams } from '@kbn/dashboard-plugin/public';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import { tagListToReferenceList } from './to_reference_list';
|
||||
import { useDateRange } from '../../hooks/use_date_range';
|
||||
|
||||
export function DashboardsTable({
|
||||
dashboards,
|
||||
|
@ -42,7 +41,7 @@ export function DashboardsTable({
|
|||
},
|
||||
},
|
||||
} = useKibana();
|
||||
const { timeRange } = useDateRange({ data });
|
||||
const { timeRange } = data.query.timefilter.timefilter.useTimefilter();
|
||||
const dashboardLocator = share.url.locators.get<DashboardLocatorParams>(DASHBOARD_APP_LOCATOR);
|
||||
const columns = useMemo((): Array<EuiBasicTableColumn<SanitizedDashboardAsset>> => {
|
||||
return [
|
||||
|
|
|
@ -37,7 +37,6 @@ import { useDashboardsFetch } from '../../hooks/use_dashboards_fetch';
|
|||
import { DashboardsTable } from '../stream_detail_dashboards_view/dashboard_table';
|
||||
import { AssetImage } from '../asset_image';
|
||||
import { useWiredStreams } from '../../hooks/use_wired_streams';
|
||||
import { useDateRange } from '../../hooks/use_date_range';
|
||||
|
||||
const formatNumber = (val: number) => {
|
||||
return Number(val).toLocaleString('en', {
|
||||
|
@ -59,9 +58,9 @@ export function StreamDetailOverview({ definition }: { definition?: IngestStream
|
|||
|
||||
const {
|
||||
timeRange,
|
||||
absoluteTimeRange: { start, end },
|
||||
setTimeRange,
|
||||
} = useDateRange({ data });
|
||||
absoluteTimeRange: { start, end },
|
||||
} = data.query.timefilter.timefilter.useTimefilter();
|
||||
|
||||
const indexPatterns = useMemo(() => {
|
||||
return getIndexPatterns(definition?.stream);
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TimeRange } from '@kbn/data-plugin/common';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export function useDateRange({ data }: { data: DataPublicPluginStart }): {
|
||||
timeRange: TimeRange;
|
||||
absoluteTimeRange: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
setTimeRange: React.Dispatch<React.SetStateAction<TimeRange>>;
|
||||
} {
|
||||
const timefilter = data.query.timefilter.timefilter;
|
||||
|
||||
const [timeRange, setTimeRange] = useState(() => timefilter.getTime());
|
||||
|
||||
const [absoluteTimeRange, setAbsoluteTimeRange] = useState(() => timefilter.getAbsoluteTime());
|
||||
|
||||
useEffect(() => {
|
||||
const timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe({
|
||||
next: () => {
|
||||
setTimeRange(() => timefilter.getTime());
|
||||
setAbsoluteTimeRange(() => timefilter.getAbsoluteTime());
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
timeUpdateSubscription.unsubscribe();
|
||||
};
|
||||
}, [timefilter]);
|
||||
|
||||
const setTimeRangeMemoized: React.Dispatch<React.SetStateAction<TimeRange>> = useCallback(
|
||||
(nextOrCallback) => {
|
||||
const val =
|
||||
typeof nextOrCallback === 'function'
|
||||
? nextOrCallback(timefilter.getTime())
|
||||
: nextOrCallback;
|
||||
|
||||
timefilter.setTime(val);
|
||||
},
|
||||
[timefilter]
|
||||
);
|
||||
|
||||
const asEpoch = useMemo(() => {
|
||||
return {
|
||||
start: new Date(absoluteTimeRange.from).getTime(),
|
||||
end: new Date(absoluteTimeRange.to).getTime(),
|
||||
};
|
||||
}, [absoluteTimeRange]);
|
||||
|
||||
return {
|
||||
timeRange,
|
||||
absoluteTimeRange: asEpoch,
|
||||
setTimeRange: setTimeRangeMemoized,
|
||||
};
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TimeRange } from '@kbn/data-plugin/common';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export function useDateRange({ data }: { data: DataPublicPluginStart }): {
|
||||
timeRange: TimeRange;
|
||||
absoluteTimeRange: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
setTimeRange: React.Dispatch<React.SetStateAction<TimeRange>>;
|
||||
} {
|
||||
const timefilter = data.query.timefilter.timefilter;
|
||||
|
||||
const [timeRange, setTimeRange] = useState(() => timefilter.getTime());
|
||||
|
||||
const [absoluteTimeRange, setAbsoluteTimeRange] = useState(() => timefilter.getAbsoluteTime());
|
||||
|
||||
useEffect(() => {
|
||||
const timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe({
|
||||
next: () => {
|
||||
setTimeRange(() => timefilter.getTime());
|
||||
setAbsoluteTimeRange(() => timefilter.getAbsoluteTime());
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
timeUpdateSubscription.unsubscribe();
|
||||
};
|
||||
}, [timefilter]);
|
||||
|
||||
const setTimeRangeMemoized: React.Dispatch<React.SetStateAction<TimeRange>> = useCallback(
|
||||
(nextOrCallback) => {
|
||||
const val =
|
||||
typeof nextOrCallback === 'function'
|
||||
? nextOrCallback(timefilter.getTime())
|
||||
: nextOrCallback;
|
||||
|
||||
timefilter.setTime(val);
|
||||
},
|
||||
[timefilter]
|
||||
);
|
||||
|
||||
const asEpoch = useMemo(() => {
|
||||
return {
|
||||
start: new Date(absoluteTimeRange.from).getTime(),
|
||||
end: new Date(absoluteTimeRange.to).getTime(),
|
||||
};
|
||||
}, [absoluteTimeRange]);
|
||||
|
||||
return {
|
||||
timeRange,
|
||||
absoluteTimeRange: asEpoch,
|
||||
setTimeRange: setTimeRangeMemoized,
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue