mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Exploratory view] Fix auto apply on date change (#114251)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
bc96e408c9
commit
d5d364724b
10 changed files with 194 additions and 64 deletions
|
@ -15,7 +15,7 @@ import { useUiSetting } from '../../../../../../../../src/plugins/kibana_react/p
|
|||
import { SeriesUrl } from '../types';
|
||||
import { ReportTypes } from '../configurations/constants';
|
||||
|
||||
export const parseAbsoluteDate = (date: string, options = {}) => {
|
||||
export const parseRelativeDate = (date: string, options = {}) => {
|
||||
return DateMath.parse(date, options)!;
|
||||
};
|
||||
export function DateRangePicker({ seriesId, series }: { seriesId: number; series: SeriesUrl }) {
|
||||
|
@ -27,12 +27,12 @@ export function DateRangePicker({ seriesId, series }: { seriesId: number; series
|
|||
|
||||
const { from: mainFrom, to: mainTo } = firstSeries!.time;
|
||||
|
||||
const startDate = parseAbsoluteDate(seriesFrom ?? mainFrom)!;
|
||||
const endDate = parseAbsoluteDate(seriesTo ?? mainTo, { roundUp: true })!;
|
||||
const startDate = parseRelativeDate(seriesFrom ?? mainFrom)!;
|
||||
const endDate = parseRelativeDate(seriesTo ?? mainTo, { roundUp: true })!;
|
||||
|
||||
const getTotalDuration = () => {
|
||||
const mainStartDate = parseAbsoluteDate(mainFrom)!;
|
||||
const mainEndDate = parseAbsoluteDate(mainTo, { roundUp: true })!;
|
||||
const mainStartDate = parseRelativeDate(mainFrom)!;
|
||||
const mainEndDate = parseRelativeDate(mainTo, { roundUp: true })!;
|
||||
return mainEndDate.diff(mainStartDate, 'millisecond');
|
||||
};
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ import {
|
|||
} from './constants';
|
||||
import { ColumnFilter, SeriesConfig, UrlFilter, URLReportDefinition } from '../types';
|
||||
import { PersistableFilter } from '../../../../../../lens/common';
|
||||
import { parseAbsoluteDate } from '../components/date_range_picker';
|
||||
import { parseRelativeDate } from '../components/date_range_picker';
|
||||
import { getDistributionInPercentageColumn } from './lens_columns/overall_column';
|
||||
|
||||
function getLayerReferenceName(layerId: string) {
|
||||
|
@ -544,11 +544,11 @@ export class LensAttributes {
|
|||
time: { from },
|
||||
} = layerConfig;
|
||||
|
||||
const inDays = Math.abs(parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'days'));
|
||||
const inDays = Math.abs(parseRelativeDate(mainFrom).diff(parseRelativeDate(from), 'days'));
|
||||
if (inDays > 1) {
|
||||
return inDays + 'd';
|
||||
}
|
||||
const inHours = Math.abs(parseAbsoluteDate(mainFrom).diff(parseAbsoluteDate(from), 'hours'));
|
||||
const inHours = Math.abs(parseRelativeDate(mainFrom).diff(parseRelativeDate(from), 'hours'));
|
||||
if (inHours === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -96,11 +96,7 @@ export function ExploratoryView({
|
|||
<Wrapper>
|
||||
{lens ? (
|
||||
<>
|
||||
<ExploratoryViewHeader
|
||||
lensAttributes={lensAttributes}
|
||||
seriesId={0}
|
||||
lastUpdated={lastUpdated}
|
||||
/>
|
||||
<ExploratoryViewHeader lensAttributes={lensAttributes} lastUpdated={lastUpdated} />
|
||||
<LensWrapper ref={wrapperRef} height={height}>
|
||||
<EuiResizableContainer
|
||||
style={{ height: '100%' }}
|
||||
|
|
|
@ -19,10 +19,7 @@ jest.spyOn(pluginHook, 'usePluginContext').mockReturnValue({
|
|||
describe('ExploratoryViewHeader', function () {
|
||||
it('should render properly', function () {
|
||||
const { getByText } = render(
|
||||
<ExploratoryViewHeader
|
||||
seriesId={0}
|
||||
lensAttributes={{ title: 'Performance distribution' } as any}
|
||||
/>
|
||||
<ExploratoryViewHeader lensAttributes={{ title: 'Performance distribution' } as any} />
|
||||
);
|
||||
getByText('Refresh');
|
||||
});
|
||||
|
|
|
@ -11,21 +11,18 @@ import { EuiBetaBadge, EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@el
|
|||
import { TypedLensByValueInput } from '../../../../../../lens/public';
|
||||
import { useSeriesStorage } from '../hooks/use_series_storage';
|
||||
import { LastUpdated } from './last_updated';
|
||||
import { combineTimeRanges } from '../lens_embeddable';
|
||||
import { ExpViewActionMenu } from '../components/action_menu';
|
||||
import { useExpViewTimeRange } from '../hooks/use_time_range';
|
||||
|
||||
interface Props {
|
||||
seriesId?: number;
|
||||
lastUpdated?: number;
|
||||
lensAttributes: TypedLensByValueInput['attributes'] | null;
|
||||
}
|
||||
|
||||
export function ExploratoryViewHeader({ seriesId, lensAttributes, lastUpdated }: Props) {
|
||||
const { getSeries, allSeries, setLastRefresh, reportType } = useSeriesStorage();
|
||||
export function ExploratoryViewHeader({ lensAttributes, lastUpdated }: Props) {
|
||||
const { setLastRefresh } = useSeriesStorage();
|
||||
|
||||
const series = seriesId ? getSeries(seriesId) : undefined;
|
||||
|
||||
const timeRange = combineTimeRanges(reportType, allSeries, series);
|
||||
const timeRange = useExpViewTimeRange();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -94,7 +94,7 @@ export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null
|
|||
if (isEmpty(indexPatterns) || isEmpty(allSeries) || !reportType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we only use the data from url to apply, since that get's updated to apply changes
|
||||
const allSeriesT: AllSeries = convertAllShortSeries(storage.get(allSeriesKey) ?? []);
|
||||
|
||||
const layerConfigs = getLayerConfigs(allSeriesT, reportType, theme, indexPatterns);
|
||||
|
|
|
@ -45,7 +45,7 @@ export function convertAllShortSeries(allShortSeries: AllShortSeries) {
|
|||
}
|
||||
|
||||
export const allSeriesKey = 'sr';
|
||||
const reportTypeKey = 'reportType';
|
||||
export const reportTypeKey = 'reportType';
|
||||
|
||||
export function UrlStorageContextProvider({
|
||||
children,
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { allSeriesKey, reportTypeKey, UrlStorageContextProvider } from './use_series_storage';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useExpViewTimeRange } from './use_time_range';
|
||||
import { ReportTypes } from '../configurations/constants';
|
||||
import { createKbnUrlStateStorage } from '../../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { TRANSACTION_DURATION } from '../configurations/constants/elasticsearch_fieldnames';
|
||||
|
||||
const mockSingleSeries = [
|
||||
{
|
||||
name: 'performance-distribution',
|
||||
dataType: 'ux',
|
||||
breakdown: 'user_agent.name',
|
||||
time: { from: 'now-15m', to: 'now' },
|
||||
selectedMetricField: TRANSACTION_DURATION,
|
||||
reportDefinitions: { 'service.name': ['elastic-co'] },
|
||||
},
|
||||
];
|
||||
|
||||
const mockMultipleSeries = [
|
||||
...mockSingleSeries,
|
||||
{
|
||||
name: 'kpi-over-time',
|
||||
dataType: 'synthetics',
|
||||
breakdown: 'user_agent.name',
|
||||
time: { from: 'now-30m', to: 'now' },
|
||||
selectedMetricField: TRANSACTION_DURATION,
|
||||
reportDefinitions: { 'service.name': ['elastic-co'] },
|
||||
},
|
||||
];
|
||||
|
||||
describe('useExpViewTimeRange', function () {
|
||||
const storage = createKbnUrlStateStorage({ useHash: false });
|
||||
|
||||
function Wrapper({ children }: { children: JSX.Element }) {
|
||||
return <UrlStorageContextProvider storage={storage}>{children}</UrlStorageContextProvider>;
|
||||
}
|
||||
it('should return expected result when there is one series', async function () {
|
||||
await storage.set(allSeriesKey, mockSingleSeries);
|
||||
await storage.set(reportTypeKey, ReportTypes.KPI);
|
||||
|
||||
const { result } = renderHook(() => useExpViewTimeRange(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toEqual({
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return expected result when there are multiple KPI series', async function () {
|
||||
await storage.set(allSeriesKey, mockMultipleSeries);
|
||||
await storage.set(reportTypeKey, ReportTypes.KPI);
|
||||
|
||||
const { result } = renderHook(() => useExpViewTimeRange(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toEqual({
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return expected result when there are multiple distribution series with relative dates', async function () {
|
||||
await storage.set(allSeriesKey, mockMultipleSeries);
|
||||
await storage.set(reportTypeKey, ReportTypes.DISTRIBUTION);
|
||||
|
||||
const { result } = renderHook(() => useExpViewTimeRange(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toEqual({
|
||||
from: 'now-30m',
|
||||
to: 'now',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return expected result when there are multiple distribution series with absolute dates', async function () {
|
||||
// from:'2021-10-11T09:55:39.551Z',to:'2021-10-11T10:55:41.516Z')))
|
||||
mockMultipleSeries[0].time.from = '2021-10-11T09:55:39.551Z';
|
||||
mockMultipleSeries[0].time.to = '2021-10-11T11:55:41.516Z';
|
||||
|
||||
mockMultipleSeries[1].time.from = '2021-01-11T09:55:39.551Z';
|
||||
mockMultipleSeries[1].time.to = '2021-10-11T10:55:41.516Z';
|
||||
|
||||
await storage.set(allSeriesKey, mockMultipleSeries);
|
||||
await storage.set(reportTypeKey, ReportTypes.DISTRIBUTION);
|
||||
|
||||
const { result } = renderHook(() => useExpViewTimeRange(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toEqual({
|
||||
from: '2021-01-11T09:55:39.551Z',
|
||||
to: '2021-10-11T11:55:41.516Z',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { isEmpty } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
AllSeries,
|
||||
allSeriesKey,
|
||||
convertAllShortSeries,
|
||||
useSeriesStorage,
|
||||
} from './use_series_storage';
|
||||
|
||||
import { ReportViewType, SeriesUrl } from '../types';
|
||||
import { ReportTypes } from '../configurations/constants';
|
||||
import { parseRelativeDate } from '../components/date_range_picker';
|
||||
|
||||
export const combineTimeRanges = (
|
||||
reportType: ReportViewType,
|
||||
allSeries: SeriesUrl[],
|
||||
firstSeries?: SeriesUrl
|
||||
) => {
|
||||
let to: string = '';
|
||||
let from: string = '';
|
||||
|
||||
if (reportType === ReportTypes.KPI) {
|
||||
return firstSeries?.time;
|
||||
}
|
||||
|
||||
allSeries.forEach((series) => {
|
||||
if (
|
||||
series.dataType &&
|
||||
series.selectedMetricField &&
|
||||
!isEmpty(series.reportDefinitions) &&
|
||||
series.time
|
||||
) {
|
||||
const seriesFrom = parseRelativeDate(series.time.from)!;
|
||||
const seriesTo = parseRelativeDate(series.time.to, { roundUp: true })!;
|
||||
|
||||
if (!to || seriesTo > parseRelativeDate(to, { roundUp: true })) {
|
||||
to = series.time.to;
|
||||
}
|
||||
if (!from || seriesFrom < parseRelativeDate(from)) {
|
||||
from = series.time.from;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { to, from };
|
||||
};
|
||||
export const useExpViewTimeRange = () => {
|
||||
const { storage, reportType, lastRefresh, firstSeries } = useSeriesStorage();
|
||||
|
||||
return useMemo(() => {
|
||||
// we only use the data from url to apply, since that get updated to apply changes
|
||||
const allSeriesFromUrl: AllSeries = convertAllShortSeries(storage.get(allSeriesKey) ?? []);
|
||||
const firstSeriesT = allSeriesFromUrl?.[0];
|
||||
|
||||
return firstSeriesT ? combineTimeRanges(reportType, allSeriesFromUrl, firstSeriesT) : undefined;
|
||||
// we want to keep last refresh in dependencies to force refresh
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [reportType, storage, lastRefresh, firstSeries]);
|
||||
};
|
|
@ -8,50 +8,16 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { TypedLensByValueInput } from '../../../../../lens/public';
|
||||
import { useSeriesStorage } from './hooks/use_series_storage';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../plugin';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ReportViewType, SeriesUrl } from './types';
|
||||
import { ReportTypes } from './configurations/constants';
|
||||
import { useExpViewTimeRange } from './hooks/use_time_range';
|
||||
|
||||
interface Props {
|
||||
lensAttributes: TypedLensByValueInput['attributes'];
|
||||
setLastUpdated: Dispatch<SetStateAction<number | undefined>>;
|
||||
}
|
||||
export const combineTimeRanges = (
|
||||
reportType: ReportViewType,
|
||||
allSeries: SeriesUrl[],
|
||||
firstSeries?: SeriesUrl
|
||||
) => {
|
||||
let to: string = '';
|
||||
let from: string = '';
|
||||
|
||||
if (reportType === ReportTypes.KPI) {
|
||||
return firstSeries?.time;
|
||||
}
|
||||
|
||||
allSeries.forEach((series) => {
|
||||
if (
|
||||
series.dataType &&
|
||||
series.selectedMetricField &&
|
||||
!isEmpty(series.reportDefinitions) &&
|
||||
series.time
|
||||
) {
|
||||
const seriesTo = new Date(series.time.to);
|
||||
const seriesFrom = new Date(series.time.from);
|
||||
if (!to || seriesTo > new Date(to)) {
|
||||
to = series.time.to;
|
||||
}
|
||||
if (!from || seriesFrom < new Date(from)) {
|
||||
from = series.time.from;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { to, from };
|
||||
};
|
||||
|
||||
export function LensEmbeddable(props: Props) {
|
||||
const { lensAttributes, setLastUpdated } = props;
|
||||
|
@ -62,11 +28,11 @@ export function LensEmbeddable(props: Props) {
|
|||
|
||||
const LensComponent = lens?.EmbeddableComponent;
|
||||
|
||||
const { firstSeries, setSeries, allSeries, reportType } = useSeriesStorage();
|
||||
const { firstSeries, setSeries, reportType } = useSeriesStorage();
|
||||
|
||||
const firstSeriesId = 0;
|
||||
|
||||
const timeRange = firstSeries ? combineTimeRanges(reportType, allSeries, firstSeries) : null;
|
||||
const timeRange = useExpViewTimeRange();
|
||||
|
||||
const onLensLoad = useCallback(() => {
|
||||
setLastUpdated(Date.now());
|
||||
|
@ -93,7 +59,7 @@ export function LensEmbeddable(props: Props) {
|
|||
[reportType, setSeries, firstSeries, notifications?.toasts]
|
||||
);
|
||||
|
||||
if (timeRange === null || !firstSeries) {
|
||||
if (!timeRange || !firstSeries) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue