diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 318f5c332a678..0920d0d716d73 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2466,7 +2466,7 @@ export interface SearchUsageCollector { // (undocumented) trackSessionExtended: () => Promise; // (undocumented) - trackSessionIndicatorTourDisabled: () => Promise; + trackSessionIndicatorSaveDisabled: () => Promise; // (undocumented) trackSessionIndicatorTourLoading: () => Promise; // (undocumented) diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.ts b/src/plugins/data/public/search/collectors/create_usage_collector.ts index c08ebcbf57cce..3fe135ea29152 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.ts @@ -38,9 +38,9 @@ export const createUsageCollector = ( METRIC_TYPE.LOADED, SEARCH_EVENT_TYPE.SESSION_INDICATOR_TOUR_RESTORED ), - trackSessionIndicatorTourDisabled: getCollector( + trackSessionIndicatorSaveDisabled: getCollector( METRIC_TYPE.LOADED, - SEARCH_EVENT_TYPE.SESSION_INDICATOR_TOUR_DISABLED + SEARCH_EVENT_TYPE.SESSION_INDICATOR_SAVE_DISABLED ), trackSessionSentToBackground: getCollector( METRIC_TYPE.CLICK, diff --git a/src/plugins/data/public/search/collectors/mocks.ts b/src/plugins/data/public/search/collectors/mocks.ts index f197d88e92eab..2a546d6310d7f 100644 --- a/src/plugins/data/public/search/collectors/mocks.ts +++ b/src/plugins/data/public/search/collectors/mocks.ts @@ -13,7 +13,7 @@ export function createSearchUsageCollectorMock(): jest.Mocked Promise; trackSessionIndicatorTourLoading: () => Promise; trackSessionIndicatorTourRestored: () => Promise; - trackSessionIndicatorTourDisabled: () => Promise; + trackSessionIndicatorSaveDisabled: () => Promise; trackSessionSentToBackground: () => Promise; trackSessionSavedResults: () => Promise; trackSessionViewRestored: () => Promise; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 16ec66cd130a6..0aef27310e090 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -30,7 +30,7 @@ const application = coreStart.application; const dataStart = dataPluginMock.createStartContract(); const sessionService = dataStart.search.session as jest.Mocked; let storage: Storage; -let usageCollector: SearchUsageCollector; +let usageCollector: jest.Mocked; const refreshInterval$ = new BehaviorSubject({ value: 0, pause: true }); const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked; @@ -232,6 +232,7 @@ describe('Completed inactivity', () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); render( @@ -263,12 +264,14 @@ describe('Completed inactivity', () => { }); expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(0); act(() => { jest.advanceTimersByTime(2.5 * 60 * 1000); }); expect(screen.getByRole('button', { name: 'Save session' })).toBeDisabled(); + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(1); }); }); @@ -318,6 +321,9 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeTruthy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(1); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(0); }); test("doesn't show tour step if state changed before delay", async () => { @@ -349,6 +355,9 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeFalsy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(0); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(0); }); }); @@ -373,6 +382,10 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeTruthy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeTruthy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(0); + expect(usageCollector.trackSessionIsRestored).toHaveBeenCalledTimes(1); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(1); }); test("doesn't show tour for irrelevant state", async () => { @@ -397,5 +410,8 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeFalsy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(0); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(0); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index fb07fe3d5ad25..7c70a270bd30a 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { debounce, distinctUntilChanged, map, mapTo, switchMap, tap } from 'rxjs/operators'; import { merge, of, timer } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; @@ -14,8 +14,8 @@ import { SearchSessionIndicator, SearchSessionIndicatorRef } from '../search_ses import { ISessionService, SearchSessionState, - SearchUsageCollector, TimefilterContract, + SearchUsageCollector, } from '../../../../../../../src/plugins/data/public'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { ApplicationStart } from '../../../../../../../src/core/public'; @@ -60,7 +60,7 @@ export const createConnectedSearchSessionIndicator = ({ ), distinctUntilChanged(), tap((value) => { - if (value) usageCollector?.trackSessionIndicatorTourDisabled(); + if (value) usageCollector?.trackSessionIndicatorSaveDisabled(); }) ); @@ -164,6 +164,12 @@ export const createConnectedSearchSessionIndicator = ({ usageCollector?.trackViewSessionsList(); }, []); + useEffect(() => { + if (state === SearchSessionState.Restored) { + usageCollector?.trackSessionIsRestored(); + } + }, [state]); + if (!sessionService.isSessionStorageReady()) return null; return ( diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx index e1ca236761a99..1568d54962eca 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx @@ -6,6 +6,7 @@ */ import { useCallback, useEffect } from 'react'; +import { once } from 'lodash'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; import { SearchSessionIndicatorRef } from '../search_session_indicator'; import { @@ -32,6 +33,26 @@ export function useSearchSessionTour( safeSet(storage, TOUR_RESTORE_STEP_KEY); }, [storage]); + // Makes sure `trackSessionIndicatorTourLoading` is called only once per sessionId + // if to call `usageCollector?.trackSessionIndicatorTourLoading()` directly inside the `useEffect` below + // it might happen that we cause excessive logging + // ESLint: React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. + // eslint-disable-next-line react-hooks/exhaustive-deps + const trackSessionIndicatorTourLoading = useCallback( + once(() => usageCollector?.trackSessionIndicatorTourLoading()), + [usageCollector, state] + ); + + // Makes sure `trackSessionIndicatorTourRestored` is called only once per sessionId + // if to call `usageCollector?.trackSessionIndicatorTourRestored()` directly inside the `useEffect` below + // it might happen that we cause excessive logging + // ESLint: React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. + // eslint-disable-next-line react-hooks/exhaustive-deps + const trackSessionIndicatorTourRestored = useCallback( + once(() => usageCollector?.trackSessionIndicatorTourRestored()), + [usageCollector, state] + ); + useEffect(() => { if (searchSessionsDisabled) return; if (!searchSessionIndicatorRef) return; @@ -40,16 +61,15 @@ export function useSearchSessionTour( if (state === SearchSessionState.Loading) { if (!safeHas(storage, TOUR_TAKING_TOO_LONG_STEP_KEY)) { timeoutHandle = window.setTimeout(() => { - usageCollector?.trackSessionIndicatorTourLoading(); + trackSessionIndicatorTourLoading(); searchSessionIndicatorRef.openPopover(); }, TOUR_TAKING_TOO_LONG_TIMEOUT); } } if (state === SearchSessionState.Restored) { - usageCollector?.trackSessionIsRestored(); if (!safeHas(storage, TOUR_RESTORE_STEP_KEY)) { - usageCollector?.trackSessionIndicatorTourRestored(); + trackSessionIndicatorTourRestored(); searchSessionIndicatorRef.openPopover(); } } @@ -65,6 +85,8 @@ export function useSearchSessionTour( markOpenedDone, markRestoredDone, usageCollector, + trackSessionIndicatorTourRestored, + trackSessionIndicatorTourLoading, ]); return {