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

[Search Session] Revamp search session indicator UI and tour. #89703

Merged
merged 31 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8edc6fa
ui revamp
Dosant Jan 29, 2021
80660c8
test
Dosant Jan 29, 2021
540b217
close popover on rerun
Dosant Jan 29, 2021
d73cc13
fix ts
Dosant Jan 29, 2021
1ee95e4
Merge branch 'master' into dev/session/ui-revamp
kibanamachine Feb 1, 2021
09d8218
Merge branch 'master' of github.com:elastic/kibana into dev/session/u…
Dosant Feb 1, 2021
36def9d
Merge branch 'dev/session/ui-revamp' of github.com:Dosant/kibana into…
Dosant Feb 1, 2021
c08c5ec
remove onRefresh from session service
Dosant Feb 1, 2021
8eeca77
fix build
Dosant Feb 1, 2021
57a184f
Merge branch 'master' into dev/session/ui-revamp
kibanamachine Feb 1, 2021
ae9c98a
fix tests
Dosant Feb 2, 2021
aef3b8e
Merge branch 'dev/session/ui-revamp' of github.com:Dosant/kibana into…
Dosant Feb 2, 2021
3907a2c
Merge branch 'master' of github.com:elastic/kibana into dev/session/u…
Dosant Feb 2, 2021
3f14a9e
update icons/copy according to latest review (2 icons missing)
Dosant Feb 2, 2021
ee62e5d
function test for tour
Dosant Feb 2, 2021
2470c3b
Merge branch 'master' of github.com:elastic/kibana into dev/session/u…
Dosant Feb 2, 2021
394b21c
use new custom icons
Dosant Feb 2, 2021
94ce812
lint
Dosant Feb 2, 2021
9259ab5
improve
Dosant Feb 2, 2021
baeb65a
Merge branch 'master' of github.com:elastic/kibana into dev/session/u…
Dosant Feb 2, 2021
270f20a
review
Dosant Feb 2, 2021
3cd9b50
fix
Dosant Feb 2, 2021
e7cbd20
fix tests
Dosant Feb 2, 2021
46a0af6
fix tour test
Dosant Feb 2, 2021
900c4f6
Merge branch 'master' into dev/session/ui-revamp
kibanamachine Feb 2, 2021
d75eaca
Merge branch 'master' of github.com:elastic/kibana into dev/session/u…
Dosant Feb 3, 2021
5aa414d
Merge branch 'master' into dev/session/ui-revamp
kibanamachine Feb 3, 2021
d4bb5a2
Merge branch 'master' into dev/session/ui-revamp
kibanamachine Feb 4, 2021
24f2916
fix tour
Dosant Feb 4, 2021
19fd9c9
Merge branch 'master' of github.com:elastic/kibana into dev/session/u…
Dosant Feb 4, 2021
30d976d
fix restored tour
Dosant Feb 4, 2021
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
1 change: 0 additions & 1 deletion src/plugins/dashboard/public/application/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ export function DashboardApp({

subscriptions.add(
merge(
data.search.session.onRefresh$,
data.query.timefilter.timefilter.getAutoRefreshFetch$(),
searchSessionIdQuery$
).subscribe(() => {
Expand Down
4 changes: 1 addition & 3 deletions src/plugins/data/public/search/session/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { BehaviorSubject, Subject } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { ISessionsClient } from './sessions_client';
import { ISessionService } from './session_service';
import { SearchSessionState } from './search_session_state';
Expand All @@ -32,8 +32,6 @@ export function getSessionServiceMock(): jest.Mocked<ISessionService> {
state$: new BehaviorSubject<SearchSessionState>(SearchSessionState.None).asObservable(),
trackSearch: jest.fn((searchDescriptor) => () => {}),
destroy: jest.fn(),
onRefresh$: new Subject(),
refresh: jest.fn(),
cancel: jest.fn(),
isStored: jest.fn(),
isRestore: jest.fn(),
Expand Down
17 changes: 1 addition & 16 deletions src/plugins/data/public/search/session/session_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { PublicContract } from '@kbn/utility-types';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
import { Observable, Subject, Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public';
import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/';
import { ConfigSchema } from '../../../config';
Expand Down Expand Up @@ -193,21 +193,6 @@ export class SessionService {
this.searchSessionIndicatorUiConfig = undefined;
}

private refresh$ = new Subject<void>();
/**
* Observable emits when search result refresh was requested
* For example, the UI could have it's own "refresh" button
* Application would use this observable to handle user interaction on that button
*/
public onRefresh$ = this.refresh$.asObservable();

/**
* Request a search results refresh
*/
public refresh() {
this.refresh$.next();
}

/**
* Request a cancellation of on-going search requests within current session
*/
Expand Down
7 changes: 0 additions & 7 deletions src/plugins/discover/public/application/angular/discover.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,6 @@ function discoverController($route, $scope, Promise) {
)
);

subscriptions.add(
data.search.session.onRefresh$.subscribe(() => {
searchSessionManager.removeSearchSessionIdFromURL({ replace: false });
refetch$.next();
})
);

$scope.changeInterval = (interval) => {
if (interval) {
setAppState({ interval });
Expand Down
10 changes: 10 additions & 0 deletions test/functional/services/common/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
);
}

/**
* Removes a value in local storage for the focused window/frame.
*
* @param {string} key
* @return {Promise<void>}
*/
public async removeLocalStorageItem(key: string): Promise<void> {
await driver.executeScript('return window.localStorage.removeItem(arguments[0]);', key);
}

/**
* Clears session storage for the focused window/frame.
*
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/data_enhanced/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { registerSearchSessionsMgmt } from './search/sessions_mgmt';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import { createConnectedSearchSessionIndicator } from './search';
import { ConfigSchema } from '../config';
import { Storage } from '../../../../src/plugins/kibana_utils/public';

export interface DataEnhancedSetupDependencies {
bfetch: BfetchPublicSetup;
Expand All @@ -37,6 +38,7 @@ export class DataEnhancedPlugin
implements Plugin<void, void, DataEnhancedSetupDependencies, DataEnhancedStartDependencies> {
private enhancedSearchInterceptor!: EnhancedSearchInterceptor;
private config!: ConfigSchema;
private readonly storage = new Storage(window.localStorage);

constructor(private initializerContext: PluginInitializerContext<ConfigSchema>) {}

Expand Down Expand Up @@ -83,6 +85,7 @@ export class DataEnhancedPlugin
sessionService: plugins.data.search.session,
application: core.application,
timeFilter: plugins.data.query.timefilter.timefilter,
storage: this.storage,
})
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import React from 'react';
import { StubBrowserStorage } from '@kbn/test/jest';
import { render, waitFor, screen, act } from '@testing-library/react';
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public/';
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
import { createConnectedSearchSessionIndicator } from './connected_search_session_indicator';
import { BehaviorSubject } from 'rxjs';
Expand All @@ -17,17 +19,19 @@ import {
TimefilterContract,
} from '../../../../../../../src/plugins/data/public';
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { TOUR_RESTORE_STEP_KEY, TOUR_TAKING_TOO_LONG_STEP_KEY } from './search_session_tour';

const coreStart = coreMock.createStart();
const dataStart = dataPluginMock.createStartContract();
const sessionService = dataStart.search.session as jest.Mocked<ISessionService>;

let storage: Storage;
const refreshInterval$ = new BehaviorSubject<RefreshInterval>({ value: 0, pause: true });
const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked<TimefilterContract>;
timeFilter.getRefreshIntervalUpdate$.mockImplementation(() => refreshInterval$);
timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue());

beforeEach(() => {
storage = new Storage(new StubBrowserStorage());
refreshInterval$.next({ value: 0, pause: true });
sessionService.isSessionStorageReady.mockImplementation(() => true);
sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({
Expand All @@ -42,21 +46,29 @@ test("shouldn't show indicator in case no active search session", async () => {
sessionService,
application: coreStart.application,
timeFilter,
storage,
});
const { getByTestId, container } = render(<SearchSessionIndicator />);

// make sure `searchSessionIndicator` isn't appearing after some time (lazy-loading)
await expect(
waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 })
).rejects.toThrow();
expect(container).toMatchInlineSnapshot(`<div />`);
expect(container).toMatchInlineSnapshot(`
<div>
<div
class="kbnRedirectCrossAppLinks"
/>
</div>
`);
});

test("shouldn't show indicator in case app hasn't opt-in", async () => {
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService,
application: coreStart.application,
timeFilter,
storage,
});
const { getByTestId, container } = render(<SearchSessionIndicator />);
sessionService.isSessionStorageReady.mockImplementation(() => false);
Expand All @@ -65,7 +77,13 @@ test("shouldn't show indicator in case app hasn't opt-in", async () => {
await expect(
waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 })
).rejects.toThrow();
expect(container).toMatchInlineSnapshot(`<div />`);
expect(container).toMatchInlineSnapshot(`
<div>
<div
class="kbnRedirectCrossAppLinks"
/>
</div>
`);
});

test('should show indicator in case there is an active search session', async () => {
Expand All @@ -74,6 +92,7 @@ test('should show indicator in case there is an active search session', async ()
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});
const { getByTestId } = render(<SearchSessionIndicator />);

Expand All @@ -98,6 +117,7 @@ test('should be disabled in case uiConfig says so ', async () => {
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});

render(<SearchSessionIndicator />);
Expand All @@ -114,6 +134,7 @@ test('should be disabled during auto-refresh', async () => {
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});

render(<SearchSessionIndicator />);
Expand All @@ -128,3 +149,107 @@ test('should be disabled during auto-refresh', async () => {

expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled();
});

describe('tour steps', () => {
describe('loading state', () => {
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

test('shows tour step on slow loading with delay', async () => {
const state$ = new BehaviorSubject(SearchSessionState.Loading);
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});
const rendered = render(<SearchSessionIndicator />);

await waitFor(() => rendered.getByTestId('searchSessionIndicator'));

expect(() => screen.getByTestId('searchSessionIndicatorPopoverContainer')).toThrow();

act(() => {
jest.advanceTimersByTime(10001);
});

expect(screen.getByTestId('searchSessionIndicatorPopoverContainer')).toBeInTheDocument();

act(() => {
jest.advanceTimersByTime(5000);
state$.next(SearchSessionState.Completed);
});

// Open tour should stay on screen after state change
expect(screen.getByTestId('searchSessionIndicatorPopoverContainer')).toBeInTheDocument();

expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy();
expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeTruthy();
});

test("doesn't show tour step if state changed before delay", async () => {
const state$ = new BehaviorSubject(SearchSessionState.Loading);
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});
const rendered = render(<SearchSessionIndicator />);

const searchSessionIndicator = await rendered.findByTestId('searchSessionIndicator');
expect(searchSessionIndicator).toBeTruthy();

act(() => {
jest.advanceTimersByTime(3000);
state$.next(SearchSessionState.Completed);
jest.advanceTimersByTime(3000);
});

expect(rendered.queryByTestId('searchSessionIndicatorPopoverContainer')).toBeFalsy();

expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy();
expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeFalsy();
});
});

test('shows tour step for restored', async () => {
const state$ = new BehaviorSubject(SearchSessionState.Restored);
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});
const rendered = render(<SearchSessionIndicator />);

await waitFor(() => rendered.getByTestId('searchSessionIndicator'));
expect(screen.getByTestId('searchSessionIndicatorPopoverContainer')).toBeInTheDocument();

expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeTruthy();
expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeTruthy();
});

test("doesn't show tour for irrelevant state", async () => {
const state$ = new BehaviorSubject(SearchSessionState.Completed);
const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ },
application: coreStart.application,
timeFilter,
storage,
});
const rendered = render(<SearchSessionIndicator />);

await waitFor(() => rendered.getByTestId('searchSessionIndicator'));

expect(rendered.queryByTestId('searchSessionIndicatorPopoverContainer')).toBeFalsy();

expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy();
expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeFalsy();
});
});
Loading