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

[Lens] Restart session if fixed now becomes outdated #88575

Merged
merged 4 commits into from
Jan 21, 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
162 changes: 136 additions & 26 deletions x-pack/plugins/lens/public/app_plugin/app.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { App } from './app';
import { LensAppProps, LensAppServices } from './types';
import { EditorFrameInstance } from '../types';
import { EditorFrameInstance, EditorFrameProps } from '../types';
import { Document } from '../persistence';
import { DOC_TYPE } from '../../common';
import { mount } from 'enzyme';
Expand Down Expand Up @@ -44,6 +44,8 @@ import {
import { LensAttributeService } from '../lens_attribute_service';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { EmbeddableStateTransfer } from '../../../../../src/plugins/embeddable/public';
import { NativeRenderer } from '../native_renderer';
import moment from 'moment';

jest.mock('../editor_frame_service/editor_frame/expression_helpers');
jest.mock('src/core/public');
Expand Down Expand Up @@ -144,6 +146,11 @@ function createMockTimefilter() {
return unsubscribe;
},
}),
calculateBounds: jest.fn(() => ({
min: moment('2021-01-10T04:00:00.000Z'),
max: moment('2021-01-10T08:00:00.000Z'),
})),
getBounds: jest.fn(() => timeFilter),
getRefreshInterval: () => {},
getRefreshIntervalDefaults: () => {},
getAutoRefreshFetch$: () => ({
Expand Down Expand Up @@ -233,6 +240,9 @@ describe('Lens App', () => {
}),
},
search: createMockSearchService(),
nowProvider: {
get: jest.fn(),
},
} as unknown) as DataPublicPluginStart,
storage: {
get: jest.fn(),
Expand Down Expand Up @@ -306,8 +316,8 @@ describe('Lens App', () => {
/>,
Object {
"dateRange": Object {
"fromDate": "now-7d",
"toDate": "now",
"fromDate": "2021-01-10T04:00:00.000Z",
"toDate": "2021-01-10T08:00:00.000Z",
},
"doc": undefined,
"filters": Array [],
Expand Down Expand Up @@ -350,7 +360,7 @@ describe('Lens App', () => {
expect(frame.mount).toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
dateRange: { fromDate: 'now-7d', toDate: 'now' },
dateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' },
query: { query: '', language: 'kuery' },
filters: [pinnedFilter],
})
Expand Down Expand Up @@ -1008,7 +1018,7 @@ describe('Lens App', () => {
expect(frame.mount).toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
dateRange: { fromDate: 'now-7d', toDate: 'now' },
dateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' },
query: { query: '', language: 'kuery' },
})
);
Expand Down Expand Up @@ -1055,7 +1065,11 @@ describe('Lens App', () => {
});

it('updates the editor frame when the user changes query or time in the search bar', () => {
const { component, frame } = mountWith({});
const { component, frame, services } = mountWith({});
(services.data.query.timefilter.timefilter.calculateBounds as jest.Mock).mockReturnValue({
min: moment('2021-01-09T04:00:00.000Z'),
max: moment('2021-01-09T08:00:00.000Z'),
});
act(() =>
component.find(TopNavMenu).prop('onQuerySubmit')!({
dateRange: { from: 'now-14d', to: 'now-7d' },
Expand All @@ -1071,10 +1085,14 @@ describe('Lens App', () => {
}),
{}
);
expect(services.data.query.timefilter.timefilter.setTime).toHaveBeenCalledWith({
from: 'now-14d',
to: 'now-7d',
});
expect(frame.mount).toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
dateRange: { fromDate: 'now-14d', toDate: 'now-7d' },
dateRange: { fromDate: '2021-01-09T04:00:00.000Z', toDate: '2021-01-09T08:00:00.000Z' },
query: { query: 'new', language: 'lucene' },
})
);
Expand Down Expand Up @@ -1237,6 +1255,34 @@ describe('Lens App', () => {
);
});

it('clears all existing unpinned filters when the active saved query is cleared', () => {
const { component, frame, services } = mountWith({});
act(() =>
component.find(TopNavMenu).prop('onQuerySubmit')!({
dateRange: { from: 'now-14d', to: 'now-7d' },
query: { query: 'new', language: 'lucene' },
})
);
const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern;
const field = ({ name: 'myfield' } as unknown) as IFieldType;
const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType;
const unpinned = esFilters.buildExistsFilter(field, indexPattern);
const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern);
FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE);
act(() => services.data.query.filterManager.setFilters([pinned, unpinned]));
component.update();
act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!());
component.update();
expect(frame.mount).toHaveBeenLastCalledWith(
expect.any(Element),
expect.objectContaining({
filters: [pinned],
})
);
});
});

describe('search session id management', () => {
it('updates the searchSessionId when the query is updated', () => {
const { component, frame } = mountWith({});
act(() => {
Expand All @@ -1263,12 +1309,12 @@ describe('Lens App', () => {
expect(frame.mount).toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
searchSessionId: `sessionId-1`,
searchSessionId: `sessionId-2`,
})
);
});

it('clears all existing unpinned filters when the active saved query is cleared', () => {
it('updates the searchSessionId when the active saved query is cleared', () => {
const { component, frame, services } = mountWith({});
act(() =>
component.find(TopNavMenu).prop('onQuerySubmit')!({
Expand All @@ -1286,31 +1332,67 @@ describe('Lens App', () => {
component.update();
act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!());
component.update();
expect(frame.mount).toHaveBeenLastCalledWith(
expect(frame.mount).toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
filters: [pinned],
searchSessionId: `sessionId-2`,
})
);
});

it('updates the searchSessionId when the active saved query is cleared', () => {
const { component, frame, services } = mountWith({});
act(() =>
component.find(TopNavMenu).prop('onQuerySubmit')!({
dateRange: { from: 'now-14d', to: 'now-7d' },
query: { query: 'new', language: 'lucene' },
const mockUpdate = {
filterableIndexPatterns: [],
doc: {
title: '',
description: '',
visualizationType: '',
state: {
datasourceStates: {},
visualization: {},
filters: [],
query: { query: '', language: 'lucene' },
},
references: [],
},
isSaveable: true,
activeData: undefined,
};

it('does not update the searchSessionId when the state changes', () => {
const { component, frame } = mountWith({});
act(() => {
(component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange(
mockUpdate
);
});
component.update();
expect(frame.mount).not.toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
searchSessionId: `sessionId-2`,
})
);
const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern;
const field = ({ name: 'myfield' } as unknown) as IFieldType;
const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType;
const unpinned = esFilters.buildExistsFilter(field, indexPattern);
const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern);
FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE);
act(() => services.data.query.filterManager.setFilters([pinned, unpinned]));
component.update();
act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!());
});

it('does update the searchSessionId when the state changes and too much time passed', () => {
const { component, frame, services } = mountWith({});

// time range is 100,000ms ago to 30,000ms ago (that's a lag of 30 percent)
(services.data.nowProvider.get as jest.Mock).mockReturnValue(new Date(Date.now() - 30000));
(services.data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({
from: 'now-2m',
to: 'now',
});
(services.data.query.timefilter.timefilter.getBounds as jest.Mock).mockReturnValue({
min: moment(Date.now() - 100000),
max: moment(Date.now() - 30000),
});

act(() => {
(component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange(
mockUpdate
);
});
component.update();
expect(frame.mount).toHaveBeenCalledWith(
expect.any(Element),
Expand All @@ -1319,6 +1401,34 @@ describe('Lens App', () => {
})
);
});

it('does not update the searchSessionId when the state changes and too little time has passed', () => {
const { component, frame, services } = mountWith({});

// time range is 100,000ms ago to 300ms ago (that's a lag of .3 percent, not enough to trigger a session update)
(services.data.nowProvider.get as jest.Mock).mockReturnValue(new Date(Date.now() - 300));
(services.data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({
from: 'now-2m',
to: 'now',
});
(services.data.query.timefilter.timefilter.getBounds as jest.Mock).mockReturnValue({
min: moment(Date.now() - 100000),
max: moment(Date.now() - 300),
});

act(() => {
(component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange(
mockUpdate
);
});
component.update();
expect(frame.mount).not.toHaveBeenCalledWith(
expect.any(Element),
expect.objectContaining({
searchSessionId: `sessionId-2`,
})
);
});
});

describe('showing a confirm message when leaving', () => {
Expand Down
15 changes: 9 additions & 6 deletions x-pack/plugins/lens/public/app_plugin/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import './app.scss';

import _ from 'lodash';
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { NotificationsStart } from 'kibana/public';
import { EuiBreadcrumb } from '@elastic/eui';
Expand Down Expand Up @@ -39,6 +39,7 @@ import {
LensByReferenceInput,
LensEmbeddableInput,
} from '../editor_frame_service/embeddable/embeddable';
import { useTimeRange } from './time_range';

export function App({
history,
Expand Down Expand Up @@ -107,9 +108,11 @@ export function App({
state.searchSessionId,
]);

// Need a stable reference for the frame component of the dateRange
const { from: fromDate, to: toDate } = data.query.timefilter.timefilter.getTime();
const currentDateRange = useMemo(() => ({ fromDate, toDate }), [fromDate, toDate]);
const { resolvedDateRange, from: fromDate, to: toDate } = useTimeRange(
data,
state.lastKnownDoc,
setState
);

const onError = useCallback(
(e: { message: string }) =>
Expand Down Expand Up @@ -658,7 +661,7 @@ export function App({
render={editorFrame.mount}
nativeProps={{
searchSessionId: state.searchSessionId,
dateRange: currentDateRange,
dateRange: resolvedDateRange,
query: state.query,
filters: state.filters,
savedQuery: state.savedQuery,
Expand All @@ -670,7 +673,7 @@ export function App({
if (isSaveable !== state.isSaveable) {
setState((s) => ({ ...s, isSaveable }));
}
if (!_.isEqual(state.persistedDoc, doc)) {
if (!_.isEqual(state.persistedDoc, doc) && !_.isEqual(state.lastKnownDoc, doc)) {
setState((s) => ({ ...s, lastKnownDoc: doc }));
}
if (!_.isEqual(state.activeData, activeData)) {
Expand Down
Loading