[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:
Shahzad 2021-10-12 16:32:40 +02:00 committed by GitHub
parent bc96e408c9
commit d5d364724b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 194 additions and 64 deletions

View file

@ -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');
};

View file

@ -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;
}

View file

@ -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%' }}

View file

@ -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');
});

View file

@ -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 (
<>

View file

@ -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);

View file

@ -45,7 +45,7 @@ export function convertAllShortSeries(allShortSeries: AllShortSeries) {
}
export const allSeriesKey = 'sr';
const reportTypeKey = 'reportType';
export const reportTypeKey = 'reportType';
export function UrlStorageContextProvider({
children,

View file

@ -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',
});
});
});

View file

@ -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]);
};

View file

@ -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;
}