mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Discover] Support EBT tracking across tabs (#224508)
## Summary This PR splits out a separate class from `DiscoverEBTManager` called `ScopedDiscoverEBTManager`, similar to #216488, in order to better support EBT tracking across tabs. The profiles tracking in EBT events is a bit convoluted, and ideally we'd be able to fully isolate the scoped managers, but our use of the global EBT context observable makes that infeasible since it's a singleton. If we simply updated the profiles in the EBT context when switching tabs, it could result in the wrong profiles being tracked for events fired asynchronously, e.g.: - Starting from tab A, create a new tab B. - Switch to tab B (which updates the EBT context with tab B's profiles) and trigger a long running search. - While the search is still running, switch back to tab A (updating the EBT context back to tab A's profiles). - Tab B's search completes while tab A is active, and the EBT context for tab B's `discoverFetchAll` event incorrectly contains tab A's profiles, since they were set when switching back to tab A. This is solved by keeping track of the active scoped manager in the root EBT manager, and temporarily updating the EBT context profiles when firing events from inactive tabs, which seems to be reliable to prevent leaking across tabs from my testing. Since I'm using the same "scoped" service approach used for context awareness across tabs, I've removed the dedicated `ScopedProfilesManagerProvider` and replaced it with a general purpose `ScopedServicesProvider` that can be used for all of these types of services. Unfortunately while Git recognized that certain files were just moved and modified (e.g. `discover_ebt_manager.test.ts`), GitHub is displaying them as entirely new files. To make it easier to review the actual file changes, open the "Changes from X commits" dropdown and select from the first commit to "Update unit tests", which will correctly display the changes before the files were moved (they weren't modified after this commit). Resolves #223943. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
998a1a2a8e
commit
8d605fd48e
51 changed files with 1232 additions and 668 deletions
|
@ -30,8 +30,7 @@
|
|||
"expressions",
|
||||
"unifiedDocViewer",
|
||||
"unifiedSearch",
|
||||
"contentManagement",
|
||||
"discoverShared"
|
||||
"contentManagement"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"dataVisualizer",
|
||||
|
|
|
@ -46,7 +46,7 @@ import type { SearchSourceDependencies } from '@kbn/data-plugin/common';
|
|||
import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { createElement } from 'react';
|
||||
import { createContextAwarenessMocks } from '../context_awareness/__mocks__';
|
||||
import { DiscoverEBTManager } from '../plugin_imports/discover_ebt_manager';
|
||||
import { DiscoverEBTManager } from '../ebt_manager';
|
||||
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
|
||||
import { createUrlTrackerMock } from './url_tracker.mock';
|
||||
|
||||
|
|
|
@ -20,13 +20,15 @@ import {
|
|||
type DiscoverCustomizationService,
|
||||
} from '../customizations';
|
||||
import { DiscoverMainProvider } from '../application/main/state_management/discover_state_provider';
|
||||
import { type ScopedProfilesManager, ScopedProfilesManagerProvider } from '../context_awareness';
|
||||
import { type ScopedProfilesManager } from '../context_awareness';
|
||||
import type { DiscoverServices } from '../build_services';
|
||||
import { createDiscoverServicesMock } from './services';
|
||||
import type { DiscoverStateContainer } from '../application/main/state_management/discover_state';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { ChartPortalsRenderer } from '../application/main/components/chart';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import type { ScopedDiscoverEBTManager } from '../ebt_manager';
|
||||
import { ScopedServicesProvider } from '../components/scoped_services_provider';
|
||||
|
||||
export const DiscoverTestProvider = ({
|
||||
services: originalServices,
|
||||
|
@ -34,6 +36,7 @@ export const DiscoverTestProvider = ({
|
|||
customizationService,
|
||||
runtimeState,
|
||||
scopedProfilesManager: originalScopedProfilesManager,
|
||||
scopedEbtManager: originalScopedEbtManager,
|
||||
currentTabId: originalCurrentTabId,
|
||||
usePortalsRenderer,
|
||||
children,
|
||||
|
@ -42,6 +45,7 @@ export const DiscoverTestProvider = ({
|
|||
stateContainer?: DiscoverStateContainer;
|
||||
customizationService?: DiscoverCustomizationService;
|
||||
scopedProfilesManager?: ScopedProfilesManager;
|
||||
scopedEbtManager?: ScopedDiscoverEBTManager;
|
||||
runtimeState?: CombinedRuntimeState;
|
||||
currentTabId?: string;
|
||||
usePortalsRenderer?: boolean;
|
||||
|
@ -51,16 +55,25 @@ export const DiscoverTestProvider = ({
|
|||
() => originalServices ?? createDiscoverServicesMock(),
|
||||
[originalServices]
|
||||
);
|
||||
const scopedEbtManager = useMemo(
|
||||
() => originalScopedEbtManager ?? services.ebtManager.createScopedEBTManager(),
|
||||
[originalScopedEbtManager, services.ebtManager]
|
||||
);
|
||||
const scopedProfilesManager = useMemo(
|
||||
() => originalScopedProfilesManager ?? services.profilesManager.createScopedProfilesManager(),
|
||||
[originalScopedProfilesManager, services.profilesManager]
|
||||
() =>
|
||||
originalScopedProfilesManager ??
|
||||
services.profilesManager.createScopedProfilesManager({ scopedEbtManager }),
|
||||
[originalScopedProfilesManager, scopedEbtManager, services.profilesManager]
|
||||
);
|
||||
const currentTabId = originalCurrentTabId ?? stateContainer?.getCurrentTab().id;
|
||||
|
||||
children = (
|
||||
<ScopedProfilesManagerProvider scopedProfilesManager={scopedProfilesManager}>
|
||||
<ScopedServicesProvider
|
||||
scopedProfilesManager={scopedProfilesManager}
|
||||
scopedEBTManager={scopedEbtManager}
|
||||
>
|
||||
{children}
|
||||
</ScopedProfilesManagerProvider>
|
||||
</ScopedServicesProvider>
|
||||
);
|
||||
|
||||
if (runtimeState) {
|
||||
|
|
|
@ -24,7 +24,6 @@ import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
|||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { generateFilters } from '@kbn/data-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils';
|
||||
import type { UseColumnsProps } from '@kbn/unified-data-table';
|
||||
import { popularizeField, useColumns } from '@kbn/unified-data-table';
|
||||
|
@ -41,6 +40,7 @@ import { ContextAppContent } from './context_app_content';
|
|||
import { SurrDocType } from './services/context';
|
||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
import { setBreadcrumbs } from '../../utils/breadcrumbs';
|
||||
import { useScopedServices } from '../../components/scoped_services_provider';
|
||||
|
||||
const ContextAppContentMemoized = memo(ContextAppContent);
|
||||
|
||||
|
@ -54,8 +54,8 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
const styles = useMemoCss(componentStyles);
|
||||
|
||||
const services = useDiscoverServices();
|
||||
const { scopedEBTManager } = useScopedServices();
|
||||
const {
|
||||
analytics,
|
||||
locator,
|
||||
uiSettings,
|
||||
capabilities,
|
||||
|
@ -63,7 +63,6 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
navigation,
|
||||
filterManager,
|
||||
core,
|
||||
ebtManager,
|
||||
fieldsMetadata,
|
||||
} = services;
|
||||
|
||||
|
@ -136,7 +135,10 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
*/
|
||||
useEffect(() => {
|
||||
const doFetch = async () => {
|
||||
const startTime = window.performance.now();
|
||||
const surroundingDocsFetchTracker = scopedEBTManager.trackPerformanceEvent(
|
||||
'discoverSurroundingDocsFetch'
|
||||
);
|
||||
|
||||
let fetchType = '';
|
||||
if (!prevAppState.current) {
|
||||
fetchType = 'all';
|
||||
|
@ -155,13 +157,8 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
await fetchContextRows();
|
||||
}
|
||||
|
||||
if (analytics && fetchType) {
|
||||
const fetchDuration = window.performance.now() - startTime;
|
||||
reportPerformanceMetricEvent(analytics, {
|
||||
eventName: 'discoverSurroundingDocsFetch',
|
||||
duration: fetchDuration,
|
||||
meta: { fetchType },
|
||||
});
|
||||
if (fetchType) {
|
||||
surroundingDocsFetchTracker.reportEvent({ meta: { fetchType } });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -170,7 +167,6 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
prevAppState.current = cloneDeep(appState);
|
||||
prevGlobalState.current = cloneDeep(globalState);
|
||||
}, [
|
||||
analytics,
|
||||
appState,
|
||||
globalState,
|
||||
anchorId,
|
||||
|
@ -178,6 +174,7 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
fetchAllRows,
|
||||
fetchSurroundingRows,
|
||||
fetchedState.anchor.id,
|
||||
scopedEBTManager,
|
||||
]);
|
||||
|
||||
const rows = useMemo(
|
||||
|
@ -209,30 +206,30 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
if (dataViews) {
|
||||
const fieldName = typeof field === 'string' ? field : field.name;
|
||||
await popularizeField(dataView, fieldName, dataViews, capabilities);
|
||||
void ebtManager.trackFilterAddition({
|
||||
void scopedEBTManager.trackFilterAddition({
|
||||
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
|
||||
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
|
||||
fieldsMetadata,
|
||||
});
|
||||
}
|
||||
},
|
||||
[filterManager, dataViews, dataView, capabilities, ebtManager, fieldsMetadata]
|
||||
[filterManager, dataView, dataViews, capabilities, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onAddColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onAddColumn(columnName);
|
||||
void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
void scopedEBTManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onAddColumn, ebtManager, fieldsMetadata]
|
||||
[onAddColumn, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onRemoveColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onRemoveColumn(columnName);
|
||||
void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
void scopedEBTManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onRemoveColumn, ebtManager, fieldsMetadata]
|
||||
[onRemoveColumn, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const TopNavMenu = navigation.ui.AggregateQueryTopNavMenu;
|
||||
|
|
|
@ -16,7 +16,8 @@ import { LoadingIndicator } from '../../components/common/loading_indicator';
|
|||
import { useDataView } from '../../hooks/use_data_view';
|
||||
import type { ContextHistoryLocationState } from './services/locator';
|
||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
import { ScopedProfilesManagerProvider, useRootProfile } from '../../context_awareness';
|
||||
import { useRootProfile } from '../../context_awareness';
|
||||
import { ScopedServicesProvider } from '../../components/scoped_services_provider';
|
||||
|
||||
export interface ContextUrlParams {
|
||||
dataViewId: string;
|
||||
|
@ -24,7 +25,7 @@ export interface ContextUrlParams {
|
|||
}
|
||||
|
||||
export function ContextAppRoute() {
|
||||
const { profilesManager, getScopedHistory } = useDiscoverServices();
|
||||
const { profilesManager, ebtManager, getScopedHistory } = useDiscoverServices();
|
||||
const scopedHistory = getScopedHistory<ContextHistoryLocationState>();
|
||||
const locationState = useMemo(
|
||||
() => scopedHistory?.location.state as ContextHistoryLocationState | undefined,
|
||||
|
@ -50,7 +51,10 @@ export function ContextAppRoute() {
|
|||
const dataViewId = decodeURIComponent(encodedDataViewId);
|
||||
const anchorId = decodeURIComponent(id);
|
||||
const { dataView, error } = useDataView({ index: locationState?.dataViewSpec || dataViewId });
|
||||
const [scopedProfilesManager] = useState(() => profilesManager.createScopedProfilesManager());
|
||||
const [scopedEbtManager] = useState(() => ebtManager.createScopedEBTManager());
|
||||
const [scopedProfilesManager] = useState(() =>
|
||||
profilesManager.createScopedProfilesManager({ scopedEbtManager })
|
||||
);
|
||||
const rootProfileState = useRootProfile();
|
||||
|
||||
if (error) {
|
||||
|
@ -80,10 +84,13 @@ export function ContextAppRoute() {
|
|||
}
|
||||
|
||||
return (
|
||||
<ScopedProfilesManagerProvider scopedProfilesManager={scopedProfilesManager}>
|
||||
<ScopedServicesProvider
|
||||
scopedProfilesManager={scopedProfilesManager}
|
||||
scopedEBTManager={scopedEbtManager}
|
||||
>
|
||||
<rootProfileState.AppWrapper>
|
||||
<ContextApp anchorId={anchorId} dataView={dataView} referrer={locationState?.referrer} />
|
||||
</rootProfileState.AppWrapper>
|
||||
</ScopedProfilesManagerProvider>
|
||||
</ScopedServicesProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
getTieBreakerFieldName,
|
||||
getEsQuerySort,
|
||||
} from '../../../../common/utils/sorting/get_es_query_sort';
|
||||
import { useScopedProfilesManager } from '../../../context_awareness';
|
||||
import { useScopedServices } from '../../../components/scoped_services_provider';
|
||||
|
||||
const createError = (statusKey: string, reason: FailureReason, error?: Error) => ({
|
||||
[statusKey]: { value: LoadingStatus.FAILED, error, reason },
|
||||
|
@ -41,7 +41,7 @@ export interface ContextAppFetchProps {
|
|||
}
|
||||
|
||||
export function useContextAppFetch({ anchorId, dataView, appState }: ContextAppFetchProps) {
|
||||
const scopedProfilesManager = useScopedProfilesManager();
|
||||
const { scopedProfilesManager } = useScopedServices();
|
||||
const services = useDiscoverServices();
|
||||
const { uiSettings: config, data, toastNotifications, filterManager } = services;
|
||||
|
||||
|
|
|
@ -34,7 +34,9 @@ describe('context app', function () {
|
|||
searchSourceStub,
|
||||
[{ '@timestamp': SortDirection.desc }, { _doc: SortDirection.desc }],
|
||||
discoverServices,
|
||||
discoverServices.profilesManager.createScopedProfilesManager()
|
||||
discoverServices.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServices.ebtManager.createScopedEBTManager(),
|
||||
})
|
||||
);
|
||||
|
||||
describe('function fetchAnchor', function () {
|
||||
|
|
|
@ -88,7 +88,9 @@ describe('context predecessors', function () {
|
|||
[],
|
||||
dataPluginMock,
|
||||
discoverServiceMock,
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager()
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -239,7 +241,9 @@ describe('context predecessors', function () {
|
|||
[],
|
||||
dataPluginMock,
|
||||
discoverServiceMock,
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager()
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -88,7 +88,9 @@ describe('context successors', function () {
|
|||
[],
|
||||
dataPluginMock,
|
||||
discoverServiceMock,
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager()
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -241,7 +243,9 @@ describe('context successors', function () {
|
|||
[],
|
||||
dataPluginMock,
|
||||
discoverServiceMock,
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager()
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -316,7 +320,9 @@ describe('context successors', function () {
|
|||
...discoverServiceMock,
|
||||
data: dataPluginMock,
|
||||
},
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager()
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
})
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ import { setBreadcrumbs } from '../../../utils/breadcrumbs';
|
|||
import { useDiscoverServices } from '../../../hooks/use_discover_services';
|
||||
import { SingleDocViewer } from './single_doc_viewer';
|
||||
import { createDataViewDataSource } from '../../../../common/data_sources';
|
||||
import { useScopedProfilesManager } from '../../../context_awareness';
|
||||
import { useScopedServices } from '../../../components/scoped_services_provider';
|
||||
|
||||
export interface DocProps extends EsDocSearchProps {
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ export interface DocProps extends EsDocSearchProps {
|
|||
|
||||
export function Doc(props: DocProps) {
|
||||
const { dataView } = props;
|
||||
const scopedProfilesManager = useScopedProfilesManager();
|
||||
const { scopedProfilesManager } = useScopedServices();
|
||||
const services = useDiscoverServices();
|
||||
const { locator, chrome, docLinks } = services;
|
||||
const indexExistsLink = docLinks.links.apis.indexExists;
|
||||
|
|
|
@ -19,7 +19,8 @@ import { useDiscoverServices } from '../../hooks/use_discover_services';
|
|||
import { DiscoverError } from '../../components/common/error_alert';
|
||||
import { useDataView } from '../../hooks/use_data_view';
|
||||
import type { DocHistoryLocationState } from './locator';
|
||||
import { ScopedProfilesManagerProvider, useRootProfile } from '../../context_awareness';
|
||||
import { useRootProfile } from '../../context_awareness';
|
||||
import { ScopedServicesProvider } from '../../components/scoped_services_provider';
|
||||
|
||||
export interface DocUrlParams {
|
||||
dataViewId: string;
|
||||
|
@ -27,7 +28,7 @@ export interface DocUrlParams {
|
|||
}
|
||||
|
||||
export const SingleDocRoute = () => {
|
||||
const { timefilter, core, profilesManager, getScopedHistory } = useDiscoverServices();
|
||||
const { timefilter, core, profilesManager, ebtManager, getScopedHistory } = useDiscoverServices();
|
||||
const { search } = useLocation();
|
||||
const { dataViewId, index } = useParams<DocUrlParams>();
|
||||
|
||||
|
@ -53,7 +54,10 @@ export const SingleDocRoute = () => {
|
|||
const { dataView, error } = useDataView({
|
||||
index: locationState?.dataViewSpec || decodeURIComponent(dataViewId),
|
||||
});
|
||||
const [scopedProfilesManager] = useState(() => profilesManager.createScopedProfilesManager());
|
||||
const [scopedEbtManager] = useState(() => ebtManager.createScopedEBTManager());
|
||||
const [scopedProfilesManager] = useState(() =>
|
||||
profilesManager.createScopedProfilesManager({ scopedEbtManager })
|
||||
);
|
||||
const rootProfileState = useRootProfile();
|
||||
|
||||
if (error) {
|
||||
|
@ -98,10 +102,13 @@ export const SingleDocRoute = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<ScopedProfilesManagerProvider scopedProfilesManager={scopedProfilesManager}>
|
||||
<ScopedServicesProvider
|
||||
scopedProfilesManager={scopedProfilesManager}
|
||||
scopedEBTManager={scopedEbtManager}
|
||||
>
|
||||
<rootProfileState.AppWrapper>
|
||||
<Doc id={id} index={index} dataView={dataView} referrer={locationState?.referrer} />
|
||||
</rootProfileState.AppWrapper>
|
||||
</ScopedProfilesManagerProvider>
|
||||
</ScopedServicesProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ import { DiscoverMainProvider } from '../../state_management/discover_state_prov
|
|||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import { useDiscoverHistogram } from './use_discover_histogram';
|
||||
import { ScopedProfilesManagerProvider } from '../../../../context_awareness';
|
||||
import { ScopedServicesProvider } from '../../../../components/scoped_services_provider';
|
||||
|
||||
export type ChartPortalNode = HtmlPortalNode;
|
||||
export type ChartPortalNodes = Record<string, ChartPortalNode>;
|
||||
|
@ -88,6 +88,7 @@ const UnifiedHistogramGuard = ({
|
|||
const currentScopedProfilesManager = useRuntimeState(
|
||||
currentTabRuntimeState.scopedProfilesManager$
|
||||
);
|
||||
const currentScopedEbtManager = useRuntimeState(currentTabRuntimeState.scopedEbtManager$);
|
||||
const currentDataView = useRuntimeState(currentTabRuntimeState.currentDataView$);
|
||||
const adHocDataViews = useRuntimeState(runtimeStateManager.adHocDataViews$);
|
||||
const isInitialized = useRef(false);
|
||||
|
@ -108,12 +109,15 @@ const UnifiedHistogramGuard = ({
|
|||
<DiscoverCustomizationProvider value={currentCustomizationService}>
|
||||
<DiscoverMainProvider value={currentStateContainer}>
|
||||
<RuntimeStateProvider currentDataView={currentDataView} adHocDataViews={adHocDataViews}>
|
||||
<ScopedProfilesManagerProvider scopedProfilesManager={currentScopedProfilesManager}>
|
||||
<ScopedServicesProvider
|
||||
scopedProfilesManager={currentScopedProfilesManager}
|
||||
scopedEBTManager={currentScopedEbtManager}
|
||||
>
|
||||
<UnifiedHistogramChartWrapper
|
||||
stateContainer={currentStateContainer}
|
||||
panelsToggle={panelsToggle}
|
||||
/>
|
||||
</ScopedProfilesManagerProvider>
|
||||
</ScopedServicesProvider>
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</DiscoverCustomizationProvider>
|
||||
|
|
|
@ -77,13 +77,15 @@ async function mountComponent(
|
|||
};
|
||||
|
||||
profilesManager = profilesManager ?? services.profilesManager;
|
||||
const scopedEbtManager = services.ebtManager.createScopedEBTManager();
|
||||
|
||||
const component = mountWithIntl(
|
||||
<DiscoverTestProvider
|
||||
services={{ ...services, profilesManager }}
|
||||
stateContainer={stateContainer}
|
||||
customizationService={customisationService}
|
||||
scopedProfilesManager={profilesManager.createScopedProfilesManager()}
|
||||
scopedProfilesManager={profilesManager.createScopedProfilesManager({ scopedEbtManager })}
|
||||
scopedEbtManager={scopedEbtManager}
|
||||
>
|
||||
<DiscoverDocuments {...props} />
|
||||
</DiscoverTestProvider>
|
||||
|
|
|
@ -83,6 +83,7 @@ import {
|
|||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
import { useScopedServices } from '../../../../components/scoped_services_provider';
|
||||
|
||||
const DiscoverGridMemoized = React.memo(DiscoverGrid);
|
||||
|
||||
|
@ -110,10 +111,11 @@ function DiscoverDocumentsComponent({
|
|||
onFieldEdited?: () => void;
|
||||
}) {
|
||||
const services = useDiscoverServices();
|
||||
const { scopedEBTManager } = useScopedServices();
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services;
|
||||
const { dataViews, capabilities, uiSettings, uiActions, fieldsMetadata } = services;
|
||||
const requestParams = useCurrentTabSelector((state) => state.dataRequestParams);
|
||||
const [
|
||||
dataSource,
|
||||
|
@ -193,17 +195,17 @@ function DiscoverDocumentsComponent({
|
|||
const onAddColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onAddColumn(columnName);
|
||||
void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
void scopedEBTManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onAddColumn, ebtManager, fieldsMetadata]
|
||||
[onAddColumn, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onRemoveColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onRemoveColumn(columnName);
|
||||
void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
void scopedEBTManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onRemoveColumn, ebtManager, fieldsMetadata]
|
||||
[onRemoveColumn, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const docViewerRef = useRef<DocViewerApi>(null);
|
||||
|
|
|
@ -62,6 +62,7 @@ import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
|||
import { useCurrentDataView, useCurrentTabSelector } from '../../state_management/redux';
|
||||
import { TABS_ENABLED } from '../../../../constants';
|
||||
import { DiscoverHistogramLayout } from './discover_histogram_layout';
|
||||
import { useScopedServices } from '../../../../components/scoped_services_provider';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||
|
@ -89,9 +90,9 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
spaces,
|
||||
observabilityAIAssistant,
|
||||
dataVisualizer: dataVisualizerService,
|
||||
ebtManager,
|
||||
fieldsMetadata,
|
||||
} = useDiscoverServices();
|
||||
const { scopedEBTManager } = useScopedServices();
|
||||
const styles = useMemoCss(componentStyles);
|
||||
|
||||
const globalQueryState = data.query.getState();
|
||||
|
@ -162,17 +163,17 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
const onAddColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onAddColumn(columnName);
|
||||
void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
void scopedEBTManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onAddColumn, ebtManager, fieldsMetadata]
|
||||
[onAddColumn, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onRemoveColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onRemoveColumn(columnName);
|
||||
void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
void scopedEBTManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onRemoveColumn, ebtManager, fieldsMetadata]
|
||||
[onRemoveColumn, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
// The assistant is getting the state from the url correctly
|
||||
|
@ -196,14 +197,22 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'filter_added');
|
||||
}
|
||||
void ebtManager.trackFilterAddition({
|
||||
void scopedEBTManager.trackFilterAddition({
|
||||
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
|
||||
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
|
||||
fieldsMetadata,
|
||||
});
|
||||
return filterManager.addFilters(newFilters);
|
||||
},
|
||||
[filterManager, dataView, dataViews, trackUiMetric, capabilities, ebtManager, fieldsMetadata]
|
||||
[
|
||||
dataView,
|
||||
dataViews,
|
||||
capabilities,
|
||||
filterManager,
|
||||
trackUiMetric,
|
||||
scopedEBTManager,
|
||||
fieldsMetadata,
|
||||
]
|
||||
);
|
||||
|
||||
const onPopulateWhereClause = useCallback<DocViewFilterFn>(
|
||||
|
@ -233,13 +242,13 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'esql_filter_added');
|
||||
}
|
||||
void ebtManager.trackFilterAddition({
|
||||
void scopedEBTManager.trackFilterAddition({
|
||||
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
|
||||
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
|
||||
fieldsMetadata,
|
||||
});
|
||||
},
|
||||
[data.query.queryString, query, trackUiMetric, ebtManager, fieldsMetadata]
|
||||
[query, data.query.queryString, trackUiMetric, scopedEBTManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onFilter = isEsqlMode ? onPopulateWhereClause : onAddFilter;
|
||||
|
|
|
@ -49,7 +49,7 @@ import { BrandedLoadingIndicator } from './branded_loading_indicator';
|
|||
import { RedirectWhenSavedObjectNotFound } from './redirect_not_found';
|
||||
import { DiscoverMainApp } from './main_app';
|
||||
import { useAsyncFunction } from '../../hooks/use_async_function';
|
||||
import { ScopedProfilesManagerProvider } from '../../../../context_awareness';
|
||||
import { ScopedServicesProvider } from '../../../../components/scoped_services_provider';
|
||||
|
||||
export interface DiscoverSessionViewProps {
|
||||
customizationContext: DiscoverCustomizationContext;
|
||||
|
@ -139,6 +139,10 @@ export const DiscoverSessionView = ({
|
|||
runtimeStateManager,
|
||||
(tab) => tab.scopedProfilesManager$
|
||||
);
|
||||
const scopedEbtManager = useCurrentTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
(tab) => tab.scopedEbtManager$
|
||||
);
|
||||
const currentDataView = useCurrentTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
(tab) => tab.currentDataView$
|
||||
|
@ -214,12 +218,7 @@ export const DiscoverSessionView = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!currentStateContainer ||
|
||||
!currentCustomizationService ||
|
||||
!scopedProfilesManager ||
|
||||
!currentDataView
|
||||
) {
|
||||
if (!currentStateContainer || !currentCustomizationService || !currentDataView) {
|
||||
return <BrandedLoadingIndicator />;
|
||||
}
|
||||
|
||||
|
@ -227,9 +226,12 @@ export const DiscoverSessionView = ({
|
|||
<DiscoverCustomizationProvider value={currentCustomizationService}>
|
||||
<DiscoverMainProvider value={currentStateContainer}>
|
||||
<RuntimeStateProvider currentDataView={currentDataView} adHocDataViews={adHocDataViews}>
|
||||
<ScopedProfilesManagerProvider scopedProfilesManager={scopedProfilesManager}>
|
||||
<ScopedServicesProvider
|
||||
scopedProfilesManager={scopedProfilesManager}
|
||||
scopedEBTManager={scopedEbtManager}
|
||||
>
|
||||
<DiscoverMainApp stateContainer={currentStateContainer} />
|
||||
</ScopedProfilesManagerProvider>
|
||||
</ScopedServicesProvider>
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</DiscoverCustomizationProvider>
|
||||
|
|
|
@ -69,7 +69,7 @@ describe('test fetchAll', () => {
|
|||
const { appState, internalState, runtimeStateManager, getCurrentTab } = getDiscoverStateMock(
|
||||
{}
|
||||
);
|
||||
const { scopedProfilesManager$ } = selectTabRuntimeState(
|
||||
const { scopedProfilesManager$, scopedEbtManager$ } = selectTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
getCurrentTab().id
|
||||
);
|
||||
|
@ -81,6 +81,7 @@ describe('test fetchAll', () => {
|
|||
appStateContainer: appState,
|
||||
internalState,
|
||||
scopedProfilesManager: scopedProfilesManager$.getValue(),
|
||||
scopedEbtManager: scopedEbtManager$.getValue(),
|
||||
searchSessionId: '123',
|
||||
initialFetchStatus: FetchStatus.UNINITIALIZED,
|
||||
savedSearch: {
|
||||
|
|
|
@ -11,7 +11,6 @@ import type { Adapters } from '@kbn/inspector-plugin/common';
|
|||
import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public';
|
||||
import type { BehaviorSubject } from 'rxjs';
|
||||
import { combineLatest, distinctUntilChanged, filter, firstValueFrom, race, switchMap } from 'rxjs';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import type { DiscoverAppStateContainer } from '../state_management/discover_app_state_container';
|
||||
|
@ -37,6 +36,7 @@ import type { DiscoverServices } from '../../../build_services';
|
|||
import { fetchEsql } from './fetch_esql';
|
||||
import type { InternalStateStore, TabState } from '../state_management/redux';
|
||||
import type { ScopedProfilesManager } from '../../../context_awareness';
|
||||
import type { ScopedDiscoverEBTManager } from '../../../ebt_manager';
|
||||
|
||||
export interface CommonFetchParams {
|
||||
dataSubjects: SavedSearchData;
|
||||
|
@ -49,6 +49,7 @@ export interface CommonFetchParams {
|
|||
searchSessionId: string;
|
||||
services: DiscoverServices;
|
||||
scopedProfilesManager: ScopedProfilesManager;
|
||||
scopedEbtManager: ScopedDiscoverEBTManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,6 +73,7 @@ export function fetchAll(
|
|||
appStateContainer,
|
||||
services,
|
||||
scopedProfilesManager,
|
||||
scopedEbtManager,
|
||||
inspectorAdapters,
|
||||
savedSearch,
|
||||
abortController,
|
||||
|
@ -123,19 +125,14 @@ export function fetchAll(
|
|||
})
|
||||
: fetchDocuments(searchSource, params);
|
||||
const fetchType = isEsqlQuery ? 'fetchTextBased' : 'fetchDocuments';
|
||||
const startTime = window.performance.now();
|
||||
const fetchAllRequestOnlyTracker = scopedEbtManager.trackPerformanceEvent(
|
||||
'discoverFetchAllRequestsOnly'
|
||||
);
|
||||
|
||||
// Handle results of the individual queries and forward the results to the corresponding dataSubjects
|
||||
response
|
||||
.then(({ records, esqlQueryColumns, interceptedWarnings = [], esqlHeaderWarning }) => {
|
||||
if (services.analytics) {
|
||||
const duration = window.performance.now() - startTime;
|
||||
reportPerformanceMetricEvent(services.analytics, {
|
||||
eventName: 'discoverFetchAllRequestsOnly',
|
||||
duration,
|
||||
meta: { fetchType },
|
||||
});
|
||||
}
|
||||
fetchAllRequestOnlyTracker.reportEvent({ meta: { fetchType } });
|
||||
|
||||
if (isEsqlQuery) {
|
||||
const fetchStatus =
|
||||
|
|
|
@ -25,7 +25,10 @@ import { selectTabRuntimeState } from '../state_management/redux';
|
|||
const getDeps = (): CommonFetchParams => {
|
||||
const { appState, internalState, dataState, runtimeStateManager, getCurrentTab } =
|
||||
getDiscoverStateMock({});
|
||||
const { scopedProfilesManager$ } = selectTabRuntimeState(runtimeStateManager, getCurrentTab().id);
|
||||
const { scopedProfilesManager$, scopedEbtManager$ } = selectTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
getCurrentTab().id
|
||||
);
|
||||
appState.update({ sampleSize: 100 });
|
||||
return {
|
||||
dataSubjects: dataState.data$,
|
||||
|
@ -38,6 +41,7 @@ const getDeps = (): CommonFetchParams => {
|
|||
internalState,
|
||||
appStateContainer: appState,
|
||||
scopedProfilesManager: scopedProfilesManager$.getValue(),
|
||||
scopedEbtManager: scopedEbtManager$.getValue(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,9 @@ describe('fetchEsql', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const scopedProfilesManager = discoverServiceMock.profilesManager.createScopedProfilesManager();
|
||||
const scopedProfilesManager = discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
});
|
||||
const fetchEsqlMockProps = {
|
||||
query: { esql: 'from *' },
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
|
|
|
@ -25,7 +25,6 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
|||
import type { AggregateQuery, Query } from '@kbn/es-query';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type { SearchResponseWarning } from '@kbn/search-response-warnings';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
import { DEFAULT_COLUMNS_SETTING, SEARCH_ON_PAGE_LOAD_SETTING } from '@kbn/discover-utils';
|
||||
|
@ -249,11 +248,10 @@ export function getDataStateContainer({
|
|||
.pipe(
|
||||
mergeMap(async ({ options }) => {
|
||||
const { id: currentTabId, resetDefaultProfileState, dataRequestParams } = getCurrentTab();
|
||||
const { scopedProfilesManager$, currentDataView$ } = selectTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
currentTabId
|
||||
);
|
||||
const { scopedProfilesManager$, scopedEbtManager$, currentDataView$ } =
|
||||
selectTabRuntimeState(runtimeStateManager, currentTabId);
|
||||
const scopedProfilesManager = scopedProfilesManager$.getValue();
|
||||
const scopedEbtManager = scopedEbtManager$.getValue();
|
||||
|
||||
const searchSessionId =
|
||||
(options.fetchMore && dataRequestParams.searchSessionId) ||
|
||||
|
@ -269,6 +267,7 @@ export function getDataStateContainer({
|
|||
internalState,
|
||||
savedSearch: savedSearchContainer.getState(),
|
||||
scopedProfilesManager,
|
||||
scopedEbtManager,
|
||||
};
|
||||
|
||||
abortController?.abort();
|
||||
|
@ -276,18 +275,14 @@ export function getDataStateContainer({
|
|||
|
||||
if (options.fetchMore) {
|
||||
abortControllerFetchMore = new AbortController();
|
||||
const fetchMoreStartTime = window.performance.now();
|
||||
const fetchMoreTracker = scopedEbtManager.trackPerformanceEvent('discoverFetchMore');
|
||||
|
||||
await fetchMoreDocuments({
|
||||
...commonFetchParams,
|
||||
abortController: abortControllerFetchMore,
|
||||
});
|
||||
|
||||
const fetchMoreDuration = window.performance.now() - fetchMoreStartTime;
|
||||
reportPerformanceMetricEvent(services.analytics, {
|
||||
eventName: 'discoverFetchMore',
|
||||
duration: fetchMoreDuration,
|
||||
});
|
||||
fetchMoreTracker.reportEvent();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -326,7 +321,7 @@ export function getDataStateContainer({
|
|||
|
||||
abortController = new AbortController();
|
||||
const prevAutoRefreshDone = autoRefreshDone;
|
||||
const fetchAllStartTime = window.performance.now();
|
||||
const fetchAllTracker = scopedEbtManager.trackPerformanceEvent('discoverFetchAll');
|
||||
|
||||
await fetchAll({
|
||||
...commonFetchParams,
|
||||
|
@ -366,11 +361,7 @@ export function getDataStateContainer({
|
|||
},
|
||||
});
|
||||
|
||||
const fetchAllDuration = window.performance.now() - fetchAllStartTime;
|
||||
reportPerformanceMetricEvent(services.analytics, {
|
||||
eventName: 'discoverFetchAll',
|
||||
duration: fetchAllDuration,
|
||||
});
|
||||
fetchAllTracker.reportEvent();
|
||||
|
||||
// If the autoRefreshCallback is still the same as when we started i.e. there was no newer call
|
||||
// replacing this current one, call it to make sure we tell that the auto refresh is done
|
||||
|
|
|
@ -76,11 +76,13 @@ export const initializeSession: InternalStateThunkActionCreator<
|
|||
dispatch(clearAllTabs());
|
||||
}
|
||||
|
||||
const discoverSessionLoadTracker =
|
||||
services.ebtManager.trackPerformanceEvent('discoverLoadSavedSearch');
|
||||
|
||||
const { currentDataView$, stateContainer$, customizationService$, scopedProfilesManager$ } =
|
||||
selectTabRuntimeState(runtimeStateManager, tabId);
|
||||
const {
|
||||
currentDataView$,
|
||||
stateContainer$,
|
||||
customizationService$,
|
||||
scopedProfilesManager$,
|
||||
scopedEbtManager$,
|
||||
} = selectTabRuntimeState(runtimeStateManager, tabId);
|
||||
const tabState = selectTab(getState(), tabId);
|
||||
|
||||
let urlState = cleanupUrlState(
|
||||
|
@ -100,7 +102,12 @@ export const initializeSession: InternalStateThunkActionCreator<
|
|||
currentDataView$.next(undefined);
|
||||
stateContainer$.next(undefined);
|
||||
customizationService$.next(undefined);
|
||||
scopedProfilesManager$.next(services.profilesManager.createScopedProfilesManager());
|
||||
scopedEbtManager$.next(services.ebtManager.createScopedEBTManager());
|
||||
scopedProfilesManager$.next(
|
||||
services.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: scopedEbtManager$.getValue(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (TABS_ENABLED && !wasTabInitialized) {
|
||||
|
@ -124,6 +131,10 @@ export const initializeSession: InternalStateThunkActionCreator<
|
|||
}
|
||||
}
|
||||
|
||||
const discoverSessionLoadTracker = scopedEbtManager$
|
||||
.getValue()
|
||||
.trackPerformanceEvent('discoverLoadSavedSearch');
|
||||
|
||||
const persistedDiscoverSession = discoverSessionId
|
||||
? await services.savedSearch.get(discoverSessionId)
|
||||
: undefined;
|
||||
|
|
|
@ -35,7 +35,7 @@ export const setTabs: InternalStateThunkActionCreator<
|
|||
(
|
||||
dispatch,
|
||||
getState,
|
||||
{ runtimeStateManager, tabsStorageManager, services: { profilesManager } }
|
||||
{ runtimeStateManager, tabsStorageManager, services: { profilesManager, ebtManager } }
|
||||
) => {
|
||||
const previousState = getState();
|
||||
const previousTabs = selectAllTabs(previousState);
|
||||
|
@ -48,7 +48,19 @@ export const setTabs: InternalStateThunkActionCreator<
|
|||
}
|
||||
|
||||
for (const tab of addedTabs) {
|
||||
runtimeStateManager.tabs.byId[tab.id] = createTabRuntimeState({ profilesManager });
|
||||
runtimeStateManager.tabs.byId[tab.id] = createTabRuntimeState({
|
||||
profilesManager,
|
||||
ebtManager,
|
||||
});
|
||||
}
|
||||
|
||||
const selectedTabRuntimeState = selectTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
params.selectedTabId
|
||||
);
|
||||
|
||||
if (selectedTabRuntimeState) {
|
||||
selectedTabRuntimeState.scopedEbtManager$.getValue().setAsActiveManager();
|
||||
}
|
||||
|
||||
dispatch(
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { DiscoverStateContainer } from '../discover_state';
|
|||
import type { ConnectedCustomizationService } from '../../../../customizations';
|
||||
import type { ProfilesManager, ScopedProfilesManager } from '../../../../context_awareness';
|
||||
import type { TabState } from './types';
|
||||
import type { DiscoverEBTManager, ScopedDiscoverEBTManager } from '../../../../ebt_manager';
|
||||
|
||||
interface DiscoverRuntimeState {
|
||||
adHocDataViews: DataView[];
|
||||
|
@ -27,6 +28,7 @@ interface TabRuntimeState {
|
|||
customizationService?: ConnectedCustomizationService;
|
||||
unifiedHistogramLayoutProps?: UnifiedHistogramPartialLayoutProps;
|
||||
scopedProfilesManager: ScopedProfilesManager;
|
||||
scopedEbtManager: ScopedDiscoverEBTManager;
|
||||
currentDataView: DataView;
|
||||
}
|
||||
|
||||
|
@ -49,19 +51,28 @@ export const createRuntimeStateManager = (): RuntimeStateManager => ({
|
|||
|
||||
export const createTabRuntimeState = ({
|
||||
profilesManager,
|
||||
ebtManager,
|
||||
}: {
|
||||
profilesManager: ProfilesManager;
|
||||
}): ReactiveTabRuntimeState => ({
|
||||
stateContainer$: new BehaviorSubject<DiscoverStateContainer | undefined>(undefined),
|
||||
customizationService$: new BehaviorSubject<ConnectedCustomizationService | undefined>(undefined),
|
||||
unifiedHistogramLayoutProps$: new BehaviorSubject<UnifiedHistogramPartialLayoutProps | undefined>(
|
||||
undefined
|
||||
),
|
||||
scopedProfilesManager$: new BehaviorSubject<ScopedProfilesManager>(
|
||||
profilesManager.createScopedProfilesManager()
|
||||
),
|
||||
currentDataView$: new BehaviorSubject<DataView | undefined>(undefined),
|
||||
});
|
||||
ebtManager: DiscoverEBTManager;
|
||||
}): ReactiveTabRuntimeState => {
|
||||
const scopedEbtManager = ebtManager.createScopedEBTManager();
|
||||
|
||||
return {
|
||||
stateContainer$: new BehaviorSubject<DiscoverStateContainer | undefined>(undefined),
|
||||
customizationService$: new BehaviorSubject<ConnectedCustomizationService | undefined>(
|
||||
undefined
|
||||
),
|
||||
unifiedHistogramLayoutProps$: new BehaviorSubject<
|
||||
UnifiedHistogramPartialLayoutProps | undefined
|
||||
>(undefined),
|
||||
scopedProfilesManager$: new BehaviorSubject(
|
||||
profilesManager.createScopedProfilesManager({ scopedEbtManager })
|
||||
),
|
||||
scopedEbtManager$: new BehaviorSubject(scopedEbtManager),
|
||||
currentDataView$: new BehaviorSubject<DataView | undefined>(undefined),
|
||||
};
|
||||
};
|
||||
|
||||
export const useRuntimeState = <T,>(stateSubject$: BehaviorSubject<T>) =>
|
||||
useObservable(stateSubject$, stateSubject$.getValue());
|
||||
|
@ -105,7 +116,7 @@ export const useCurrentTabRuntimeState = <T,>(
|
|||
};
|
||||
|
||||
export type CombinedRuntimeState = DiscoverRuntimeState &
|
||||
Omit<TabRuntimeState, 'scopedProfilesManager'>;
|
||||
Omit<TabRuntimeState, 'scopedProfilesManager' | 'scopedEbtManager'>;
|
||||
|
||||
const runtimeStateContext = createContext<CombinedRuntimeState | undefined>(undefined);
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ const emptyDataView = buildDataViewMock({
|
|||
name: 'emptyDataView',
|
||||
fields: fieldList(),
|
||||
});
|
||||
const { profilesManagerMock } = createContextAwarenessMocks();
|
||||
const scopedProfilesManager = profilesManagerMock.createScopedProfilesManager();
|
||||
const { profilesManagerMock, scopedEbtManagerMock } = createContextAwarenessMocks();
|
||||
const scopedProfilesManager = profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: scopedEbtManagerMock,
|
||||
});
|
||||
|
||||
scopedProfilesManager.resolveDataSourceProfile({});
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ import type { DiscoverContextAppLocator } from './application/context/services/l
|
|||
import type { DiscoverSingleDocLocator } from './application/doc/locator';
|
||||
import type { DiscoverAppLocator } from '../common';
|
||||
import type { ProfilesManager } from './context_awareness';
|
||||
import type { DiscoverEBTManager } from './plugin_imports/discover_ebt_manager';
|
||||
import type { DiscoverEBTManager } from './ebt_manager';
|
||||
|
||||
/**
|
||||
* Location state of internal Discover history instance
|
||||
|
|
|
@ -491,7 +491,9 @@ describe('Discover flyout', function () {
|
|||
_source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' },
|
||||
},
|
||||
];
|
||||
const scopedProfilesManager = services.profilesManager.createScopedProfilesManager();
|
||||
const scopedProfilesManager = services.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: services.ebtManager.createScopedEBTManager(),
|
||||
});
|
||||
const records = buildDataTableRecordList({
|
||||
records: hits as EsHitRecord[],
|
||||
dataView: dataViewMock,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { ScopedServicesProvider, useScopedServices } from './scoped_services_provider';
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react';
|
||||
import type { ScopedProfilesManager } from '../../context_awareness';
|
||||
import type { ScopedDiscoverEBTManager } from '../../ebt_manager';
|
||||
|
||||
interface ScopedServices {
|
||||
scopedProfilesManager: ScopedProfilesManager;
|
||||
scopedEBTManager: ScopedDiscoverEBTManager;
|
||||
}
|
||||
|
||||
const scopedServicesContext = createContext<ScopedServices | undefined>(undefined);
|
||||
|
||||
export const ScopedServicesProvider = ({
|
||||
scopedProfilesManager,
|
||||
scopedEBTManager,
|
||||
children,
|
||||
}: PropsWithChildren<ScopedServices>) => {
|
||||
const scopedServices = useMemo(
|
||||
() => ({ scopedProfilesManager, scopedEBTManager }),
|
||||
[scopedEBTManager, scopedProfilesManager]
|
||||
);
|
||||
|
||||
return (
|
||||
<scopedServicesContext.Provider value={scopedServices}>
|
||||
{children}
|
||||
</scopedServicesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useScopedServices = () => {
|
||||
const context = useContext(scopedServicesContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useScopedServices must be used within a ScopedServicesProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
|
@ -25,7 +25,7 @@ import {
|
|||
} from '../profiles';
|
||||
import type { ProfileProviderServices } from '../profile_providers/profile_provider_services';
|
||||
import { ProfilesManager } from '../profiles_manager';
|
||||
import { DiscoverEBTManager } from '../../plugin_imports/discover_ebt_manager';
|
||||
import { DiscoverEBTManager } from '../../ebt_manager';
|
||||
import {
|
||||
createApmErrorsContextServiceMock,
|
||||
createLogsContextServiceMock,
|
||||
|
@ -169,12 +169,11 @@ export const createContextAwarenessMocks = ({
|
|||
documentProfileServiceMock.registerProvider(documentProfileProviderMock);
|
||||
}
|
||||
|
||||
const ebtManagerMock = new DiscoverEBTManager();
|
||||
const scopedEbtManagerMock = new DiscoverEBTManager().createScopedEBTManager();
|
||||
const profilesManagerMock = new ProfilesManager(
|
||||
rootProfileServiceMock,
|
||||
dataSourceProfileServiceMock,
|
||||
documentProfileServiceMock,
|
||||
ebtManagerMock
|
||||
documentProfileServiceMock
|
||||
);
|
||||
|
||||
const profileProviderServices = createProfileProviderServicesMock();
|
||||
|
@ -190,7 +189,7 @@ export const createContextAwarenessMocks = ({
|
|||
contextRecordMock2,
|
||||
profilesManagerMock,
|
||||
profileProviderServices,
|
||||
ebtManagerMock,
|
||||
scopedEbtManagerMock,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -31,11 +31,14 @@ import {
|
|||
import { createContextAwarenessMocks } from '../__mocks__';
|
||||
import { type ScopedProfilesManager } from '../profiles_manager';
|
||||
import { DiscoverTestProvider } from '../../__mocks__/test_provider';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { ScopedDiscoverEBTManager } from '../../ebt_manager';
|
||||
|
||||
let mockScopedProfilesManager: ScopedProfilesManager;
|
||||
let mockScopedEbtManager: ScopedDiscoverEBTManager;
|
||||
let mockUuid = 0;
|
||||
|
||||
jest.mock('uuid', () => ({ ...jest.requireActual('uuid'), v4: () => (++mockUuid).toString() }));
|
||||
jest.mock('uuid', () => ({ ...jest.requireActual('uuid'), v4: jest.fn() }));
|
||||
|
||||
const mockActions: Array<ActionDefinition<DiscoverCellActionExecutionContext>> = [];
|
||||
const mockTriggerActions: Record<string, string[]> = { [DISCOVER_CELL_ACTIONS_TRIGGER.id]: [] };
|
||||
|
@ -83,6 +86,7 @@ describe('useAdditionalCellActions', () => {
|
|||
<DiscoverTestProvider
|
||||
services={discoverServiceMock}
|
||||
scopedProfilesManager={mockScopedProfilesManager}
|
||||
scopedEbtManager={mockScopedEbtManager}
|
||||
>
|
||||
{children}
|
||||
</DiscoverTestProvider>
|
||||
|
@ -91,13 +95,19 @@ describe('useAdditionalCellActions', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
discoverServiceMock.profilesManager = createContextAwarenessMocks().profilesManagerMock;
|
||||
mockScopedProfilesManager = discoverServiceMock.profilesManager.createScopedProfilesManager();
|
||||
(uuidv4 as jest.Mock).mockImplementation(jest.requireActual('uuid').v4);
|
||||
const { profilesManagerMock, scopedEbtManagerMock } = createContextAwarenessMocks();
|
||||
discoverServiceMock.profilesManager = profilesManagerMock;
|
||||
mockScopedEbtManager = scopedEbtManagerMock;
|
||||
mockScopedProfilesManager = discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: mockScopedEbtManager,
|
||||
});
|
||||
(uuidv4 as jest.Mock).mockImplementation(() => (++mockUuid).toString());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockUuid = 0;
|
||||
jest.clearAllMocks();
|
||||
mockUuid = 0;
|
||||
});
|
||||
|
||||
it('should return metadata', async () => {
|
||||
|
|
|
@ -28,6 +28,7 @@ const {
|
|||
contextRecordMock,
|
||||
contextRecordMock2,
|
||||
profilesManagerMock,
|
||||
scopedEbtManagerMock,
|
||||
} = createContextAwarenessMocks({ shouldRegisterProviders: false });
|
||||
|
||||
rootProfileServiceMock.registerProvider({
|
||||
|
@ -46,7 +47,9 @@ rootProfileServiceMock.registerProvider(rootProfileProviderMock);
|
|||
dataSourceProfileServiceMock.registerProvider(dataSourceProfileProviderMock);
|
||||
documentProfileServiceMock.registerProvider(documentProfileProviderMock);
|
||||
|
||||
const scopedProfilesManager = profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: scopedEbtManagerMock,
|
||||
});
|
||||
const record = scopedProfilesManager.resolveDocumentProfile({ record: contextRecordMock });
|
||||
const record2 = scopedProfilesManager.resolveDocumentProfile({ record: contextRecordMock2 });
|
||||
const services = createDiscoverServicesMock();
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useScopedProfilesManager, type GetProfilesOptions } from '../profiles_manager';
|
||||
import { type GetProfilesOptions } from '../profiles_manager';
|
||||
import { useScopedServices } from '../../components/scoped_services_provider';
|
||||
|
||||
/**
|
||||
* Hook to retreive the resolved profiles
|
||||
|
@ -16,7 +17,7 @@ import { useScopedProfilesManager, type GetProfilesOptions } from '../profiles_m
|
|||
* @returns The resolved profiles
|
||||
*/
|
||||
export const useProfiles = ({ record }: GetProfilesOptions = {}) => {
|
||||
const scopedProfilesManager = useScopedProfilesManager();
|
||||
const { scopedProfilesManager } = useScopedServices();
|
||||
const [profiles, setProfiles] = useState(() => scopedProfilesManager.getProfiles({ record }));
|
||||
const profiles$ = useMemo(
|
||||
() => scopedProfilesManager.getProfiles$({ record }),
|
||||
|
|
|
@ -13,8 +13,6 @@ export { getMergedAccessor } from './composable_profile';
|
|||
export {
|
||||
ProfilesManager,
|
||||
ScopedProfilesManager,
|
||||
ScopedProfilesManagerProvider,
|
||||
useScopedProfilesManager,
|
||||
ContextualProfileLevel,
|
||||
type GetProfilesOptions,
|
||||
} from './profiles_manager';
|
||||
|
|
|
@ -9,5 +9,4 @@
|
|||
|
||||
export { ProfilesManager } from './profiles_manager';
|
||||
export { ScopedProfilesManager, type GetProfilesOptions } from './scoped_profiles_manager';
|
||||
export { ScopedProfilesManagerProvider, useScopedProfilesManager } from './provider';
|
||||
export { ContextualProfileLevel } from './consts';
|
||||
|
|
|
@ -25,25 +25,31 @@ describe('ProfilesManager', () => {
|
|||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mocks = createContextAwarenessMocks();
|
||||
jest.spyOn(mocks.ebtManagerMock, 'updateProfilesContextWith');
|
||||
jest.spyOn(mocks.ebtManagerMock, 'trackContextualProfileResolvedEvent');
|
||||
jest.spyOn(mocks.scopedEbtManagerMock, 'updateProfilesContextWith');
|
||||
jest.spyOn(mocks.scopedEbtManagerMock, 'trackContextualProfileResolvedEvent');
|
||||
});
|
||||
|
||||
it('should return default profiles', () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
const profiles = scopedProfilesManager.getProfiles();
|
||||
expect(profiles).toEqual([{}, {}, {}]);
|
||||
});
|
||||
|
||||
it('should resolve root profile', async () => {
|
||||
await mocks.profilesManagerMock.resolveRootProfile({});
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
const profiles = scopedProfilesManager.getProfiles();
|
||||
expect(profiles).toEqual([toAppliedProfile(mocks.rootProfileProviderMock.profile), {}, {}]);
|
||||
});
|
||||
|
||||
it('should resolve data source profile', async () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
await scopedProfilesManager.resolveDataSourceProfile({});
|
||||
const profiles = scopedProfilesManager.getProfiles();
|
||||
expect(profiles).toEqual([
|
||||
|
@ -54,7 +60,9 @@ describe('ProfilesManager', () => {
|
|||
});
|
||||
|
||||
it('should resolve document profile', async () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
const record = scopedProfilesManager.resolveDocumentProfile({
|
||||
record: mocks.contextRecordMock,
|
||||
});
|
||||
|
@ -64,7 +72,9 @@ describe('ProfilesManager', () => {
|
|||
|
||||
it('should resolve multiple profiles', async () => {
|
||||
await mocks.profilesManagerMock.resolveRootProfile({});
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
await scopedProfilesManager.resolveDataSourceProfile({});
|
||||
const record = scopedProfilesManager.resolveDocumentProfile({
|
||||
record: mocks.contextRecordMock,
|
||||
|
@ -76,23 +86,31 @@ describe('ProfilesManager', () => {
|
|||
toAppliedProfile(mocks.documentProfileProviderMock.profile),
|
||||
]);
|
||||
|
||||
expect(mocks.ebtManagerMock.updateProfilesContextWith).toHaveBeenCalledWith([
|
||||
expect(mocks.scopedEbtManagerMock.updateProfilesContextWith).toHaveBeenCalledWith([
|
||||
'root-profile',
|
||||
'data-source-profile',
|
||||
]);
|
||||
|
||||
expect(mocks.ebtManagerMock.trackContextualProfileResolvedEvent).toHaveBeenNthCalledWith(1, {
|
||||
profileId: 'root-profile',
|
||||
contextLevel: 'rootLevel',
|
||||
});
|
||||
expect(mocks.ebtManagerMock.trackContextualProfileResolvedEvent).toHaveBeenNthCalledWith(2, {
|
||||
profileId: 'data-source-profile',
|
||||
contextLevel: 'dataSourceLevel',
|
||||
});
|
||||
expect(mocks.scopedEbtManagerMock.trackContextualProfileResolvedEvent).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{
|
||||
profileId: 'root-profile',
|
||||
contextLevel: 'rootLevel',
|
||||
}
|
||||
);
|
||||
expect(mocks.scopedEbtManagerMock.trackContextualProfileResolvedEvent).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{
|
||||
profileId: 'data-source-profile',
|
||||
contextLevel: 'dataSourceLevel',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should expose profiles as an observable', async () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
const getProfilesSpy = jest.spyOn(scopedProfilesManager, 'getProfiles');
|
||||
const record = scopedProfilesManager.resolveDocumentProfile({
|
||||
record: mocks.contextRecordMock,
|
||||
|
@ -135,7 +153,9 @@ describe('ProfilesManager', () => {
|
|||
});
|
||||
|
||||
it('should not resolve data source profile again if params have not changed', async () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
await scopedProfilesManager.resolveDataSourceProfile({
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'from *' },
|
||||
|
@ -149,7 +169,9 @@ describe('ProfilesManager', () => {
|
|||
});
|
||||
|
||||
it('should resolve data source profile again if params have changed', async () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
await scopedProfilesManager.resolveDataSourceProfile({
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'from *' },
|
||||
|
@ -164,7 +186,9 @@ describe('ProfilesManager', () => {
|
|||
|
||||
it('should log an error and fall back to the default profile if root profile resolution fails', async () => {
|
||||
await mocks.profilesManagerMock.resolveRootProfile({ solutionNavId: 'solutionNavId' });
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
let profiles = scopedProfilesManager.getProfiles();
|
||||
expect(profiles).toEqual([toAppliedProfile(mocks.rootProfileProviderMock.profile), {}, {}]);
|
||||
const resolveSpy = jest.spyOn(mocks.rootProfileProviderMock, 'resolve');
|
||||
|
@ -179,7 +203,9 @@ describe('ProfilesManager', () => {
|
|||
});
|
||||
|
||||
it('should log an error and fall back to the default profile if data source profile resolution fails', async () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
await scopedProfilesManager.resolveDataSourceProfile({
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'from *' },
|
||||
|
@ -205,7 +231,9 @@ describe('ProfilesManager', () => {
|
|||
});
|
||||
|
||||
it('should log an error and fall back to the default profile if document profile resolution fails', () => {
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
const record = scopedProfilesManager.resolveDocumentProfile({
|
||||
record: mocks.contextRecordMock,
|
||||
});
|
||||
|
@ -224,7 +252,7 @@ describe('ProfilesManager', () => {
|
|||
new Error('Failed to resolve')
|
||||
);
|
||||
expect(profiles).toEqual([{}, {}, {}]);
|
||||
expect(mocks.ebtManagerMock.trackContextualProfileResolvedEvent).toHaveBeenCalledWith({
|
||||
expect(mocks.scopedEbtManagerMock.trackContextualProfileResolvedEvent).toHaveBeenCalledWith({
|
||||
profileId: 'document-profile',
|
||||
contextLevel: 'documentLevel',
|
||||
});
|
||||
|
@ -245,7 +273,9 @@ describe('ProfilesManager', () => {
|
|||
});
|
||||
expect(resolveSpy).toHaveReturnedTimes(1);
|
||||
expect(resolveSpy).toHaveLastReturnedWith(deferredResult);
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
expect(scopedProfilesManager.getProfiles()).toEqual([{}, {}, {}]);
|
||||
const resolvedDeferredResult2$ = new Subject();
|
||||
const deferredResult2 = firstValueFrom(resolvedDeferredResult2$).then(() => newContext);
|
||||
|
@ -284,7 +314,9 @@ describe('ProfilesManager', () => {
|
|||
const resolvedDeferredResult$ = new Subject();
|
||||
const deferredResult = firstValueFrom(resolvedDeferredResult$).then(() => context);
|
||||
resolveSpy.mockResolvedValueOnce(deferredResult);
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager();
|
||||
const scopedProfilesManager = mocks.profilesManagerMock.createScopedProfilesManager({
|
||||
scopedEbtManager: mocks.scopedEbtManagerMock,
|
||||
});
|
||||
const promise1 = scopedProfilesManager.resolveDataSourceProfile({
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'from *' },
|
||||
|
|
|
@ -17,7 +17,7 @@ import type {
|
|||
RootContext,
|
||||
} from '../profiles';
|
||||
import type { ContextWithProfileId } from '../profile_service';
|
||||
import type { DiscoverEBTManager } from '../../plugin_imports/discover_ebt_manager';
|
||||
import type { ScopedDiscoverEBTManager } from '../../ebt_manager';
|
||||
import type { AppliedProfile } from '../composable_profile';
|
||||
import { logResolutionError } from './utils';
|
||||
import { ScopedProfilesManager } from './scoped_profiles_manager';
|
||||
|
@ -51,8 +51,7 @@ export class ProfilesManager {
|
|||
constructor(
|
||||
private readonly rootProfileService: RootProfileService,
|
||||
private readonly dataSourceProfileService: DataSourceProfileService,
|
||||
private readonly documentProfileService: DocumentProfileService,
|
||||
private readonly ebtManager: DiscoverEBTManager
|
||||
private readonly documentProfileService: DocumentProfileService
|
||||
) {
|
||||
this.rootContext$ = new BehaviorSubject(rootProfileService.defaultContext);
|
||||
this.rootProfile = rootProfileService.getProfile({ context: this.rootContext$.getValue() });
|
||||
|
@ -110,13 +109,17 @@ export class ProfilesManager {
|
|||
* Creates a profiles manager instance scoped to a single tab with a shared root context
|
||||
* @returns The scoped profiles manager
|
||||
*/
|
||||
public createScopedProfilesManager() {
|
||||
public createScopedProfilesManager({
|
||||
scopedEbtManager,
|
||||
}: {
|
||||
scopedEbtManager: ScopedDiscoverEBTManager;
|
||||
}) {
|
||||
return new ScopedProfilesManager(
|
||||
this.rootContext$,
|
||||
() => this.rootProfile,
|
||||
this.dataSourceProfileService,
|
||||
this.documentProfileService,
|
||||
this.ebtManager
|
||||
scopedEbtManager
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { type PropsWithChildren, createContext, useContext } from 'react';
|
||||
import type { ScopedProfilesManager } from './scoped_profiles_manager';
|
||||
|
||||
const scopedProfilesManagerContext = createContext<ScopedProfilesManager | undefined>(undefined);
|
||||
|
||||
export const ScopedProfilesManagerProvider = ({
|
||||
scopedProfilesManager,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
scopedProfilesManager: ScopedProfilesManager;
|
||||
}>) => (
|
||||
<scopedProfilesManagerContext.Provider value={scopedProfilesManager}>
|
||||
{children}
|
||||
</scopedProfilesManagerContext.Provider>
|
||||
);
|
||||
|
||||
export const useScopedProfilesManager = () => {
|
||||
const context = useContext(scopedProfilesManagerContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useScopedProfilesManager must be used within a ScopedProfilesManagerProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
|
@ -24,7 +24,7 @@ import type {
|
|||
DocumentProfileService,
|
||||
} from '../profiles/document_profile';
|
||||
import type { RootContext } from '../profiles/root_profile';
|
||||
import type { DiscoverEBTManager } from '../../plugin_imports/discover_ebt_manager';
|
||||
import type { ScopedDiscoverEBTManager } from '../../ebt_manager';
|
||||
import { logResolutionError } from './utils';
|
||||
import { DataSourceType, isDataSourceType } from '../../../common/data_sources';
|
||||
import { ContextualProfileLevel } from './consts';
|
||||
|
@ -60,7 +60,7 @@ export class ScopedProfilesManager {
|
|||
private readonly getRootProfile: () => AppliedProfile,
|
||||
private readonly dataSourceProfileService: DataSourceProfileService,
|
||||
private readonly documentProfileService: DocumentProfileService,
|
||||
private readonly ebtManager: DiscoverEBTManager
|
||||
private readonly scopedEbtManager: ScopedDiscoverEBTManager
|
||||
) {
|
||||
this.dataSourceContext$ = new BehaviorSubject(dataSourceProfileService.defaultContext);
|
||||
this.dataSourceProfile = dataSourceProfileService.getProfile({
|
||||
|
@ -143,7 +143,7 @@ export class ScopedProfilesManager {
|
|||
}
|
||||
}
|
||||
|
||||
this.ebtManager.trackContextualProfileResolvedEvent({
|
||||
this.scopedEbtManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.documentLevel,
|
||||
profileId: context.profileId,
|
||||
});
|
||||
|
@ -187,16 +187,16 @@ export class ScopedProfilesManager {
|
|||
private trackActiveProfiles(rootContextProfileId: string, dataSourceContextProfileId: string) {
|
||||
const dscProfiles = [rootContextProfileId, dataSourceContextProfileId];
|
||||
|
||||
this.ebtManager.trackContextualProfileResolvedEvent({
|
||||
this.scopedEbtManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: rootContextProfileId,
|
||||
});
|
||||
this.ebtManager.trackContextualProfileResolvedEvent({
|
||||
this.scopedEbtManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.dataSourceLevel,
|
||||
profileId: dataSourceContextProfileId,
|
||||
});
|
||||
|
||||
this.ebtManager.updateProfilesContextWith(dscProfiles);
|
||||
this.scopedEbtManager.updateProfilesContextWith(dscProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,635 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, skip } from 'rxjs';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { type DiscoverEBTContextProps, DiscoverEBTManager } from '.';
|
||||
import { registerDiscoverEBTManagerAnalytics } from './discover_ebt_manager_registrations';
|
||||
import { ContextualProfileLevel } from '../context_awareness/profiles_manager';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
|
||||
jest.mock('@kbn/ebt-tools', () => ({
|
||||
...jest.requireActual('@kbn/ebt-tools'),
|
||||
reportPerformanceMetricEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('DiscoverEBTManager', () => {
|
||||
let discoverEBTContextManager: DiscoverEBTManager;
|
||||
let discoverEbtContext$: BehaviorSubject<DiscoverEBTContextProps>;
|
||||
|
||||
const coreSetupMock = coreMock.createSetup();
|
||||
|
||||
const fieldsMetadata = {
|
||||
getClient: jest.fn().mockResolvedValue({
|
||||
find: jest.fn().mockResolvedValue({
|
||||
fields: {
|
||||
test: {
|
||||
short: 'test',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
} as unknown as FieldsMetadataPublicStart;
|
||||
|
||||
beforeEach(() => {
|
||||
discoverEBTContextManager = new DiscoverEBTManager();
|
||||
discoverEbtContext$ = new BehaviorSubject<DiscoverEBTContextProps>({
|
||||
discoverProfiles: [],
|
||||
});
|
||||
(coreSetupMock.analytics.reportEvent as jest.Mock).mockClear();
|
||||
(reportPerformanceMetricEvent as jest.Mock).mockClear();
|
||||
jest.spyOn(window.performance, 'now').mockRestore();
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register the context provider and custom events', () => {
|
||||
registerDiscoverEBTManagerAnalytics(coreSetupMock, discoverEbtContext$);
|
||||
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.registerContextProvider).toHaveBeenCalledWith({
|
||||
name: 'discover_context',
|
||||
context$: expect.any(BehaviorSubject),
|
||||
schema: {
|
||||
discoverProfiles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'List of active Discover context awareness profiles',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.registerEventType).toHaveBeenCalledWith({
|
||||
eventType: 'discover_field_usage',
|
||||
schema: {
|
||||
eventName: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description:
|
||||
'The name of the event that is tracked in the metrics i.e. dataTableSelection, dataTableRemoval',
|
||||
},
|
||||
},
|
||||
fieldName: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: "Field name if it's a part of ECS schema",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
filterOperation: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: "Operation type when a filter is added i.e. '+', '-', '_exists_'",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateProfilesWith', () => {
|
||||
it('should update the profiles with the provided props', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
scopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles2);
|
||||
});
|
||||
|
||||
it('should not update the profiles if profile list did not change', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
scopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
});
|
||||
|
||||
it('should not update the profiles if not enabled yet', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not update the profiles after resetting unless enabled again', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
discoverEBTContextManager.onDiscoverAppUnmounted();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
});
|
||||
|
||||
it('should not update the profiles if there is no active scoped manager', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should update the profiles when activating a scoped manager', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
scopedManager.setAsActiveManager();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
});
|
||||
|
||||
it('should update the profiles when changing the active scoped manager', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
const anotherScopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
anotherScopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
anotherScopedManager.setAsActiveManager();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles2);
|
||||
});
|
||||
|
||||
it('should not update the profiles for inactive scoped managers', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
const anotherScopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
anotherScopedManager.setAsActiveManager();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
scopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDiscoverAppMounted/onDiscoverAppUnmounted', () => {
|
||||
it('should clear the active scoped manager after unmounting and remounting', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
discoverEBTContextManager.onDiscoverAppUnmounted();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
scopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
scopedManager.setAsActiveManager();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackFieldUsageEvent', () => {
|
||||
it('should track the field usage when a field is added to the table', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
await scopedManager.trackDataTableSelection({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableSelection',
|
||||
fieldName: 'test',
|
||||
});
|
||||
|
||||
await scopedManager.trackDataTableSelection({
|
||||
fieldName: 'test2',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenLastCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableSelection', // non-ECS fields would not be included in properties
|
||||
});
|
||||
});
|
||||
|
||||
it('should track the field usage when a field is removed from the table', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
await scopedManager.trackDataTableRemoval({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableRemoval',
|
||||
fieldName: 'test',
|
||||
});
|
||||
|
||||
await scopedManager.trackDataTableRemoval({
|
||||
fieldName: 'test2',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenLastCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableRemoval', // non-ECS fields would not be included in properties
|
||||
});
|
||||
});
|
||||
|
||||
it('should track the field usage when a filter is created', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
await scopedManager.trackFilterAddition({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
filterOperation: '+',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledWith('discover_field_usage', {
|
||||
eventName: 'filterAddition',
|
||||
fieldName: 'test',
|
||||
filterOperation: '+',
|
||||
});
|
||||
|
||||
await scopedManager.trackFilterAddition({
|
||||
fieldName: 'test2',
|
||||
fieldsMetadata,
|
||||
filterOperation: '_exists_',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenLastCalledWith('discover_field_usage', {
|
||||
eventName: 'filterAddition', // non-ECS fields would not be included in properties
|
||||
filterOperation: '_exists_',
|
||||
});
|
||||
});
|
||||
|
||||
it('should temporarily update the discoverEbtContext$ when tracking field usage in an inactive scoped manager', async () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
const anotherScopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
anotherScopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
const results: unknown[] = [];
|
||||
|
||||
discoverEbtContext$.pipe(skip(1)).subscribe(({ discoverProfiles }) => {
|
||||
results.push(discoverProfiles);
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(coreSetupMock.analytics, 'reportEvent')
|
||||
.mockImplementation((eventType, eventData) => {
|
||||
results.push({ eventType, eventData });
|
||||
});
|
||||
|
||||
await anotherScopedManager.trackDataTableSelection({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
});
|
||||
await anotherScopedManager.trackDataTableRemoval({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
});
|
||||
await anotherScopedManager.trackFilterAddition({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
filterOperation: '+',
|
||||
});
|
||||
|
||||
expect(results).toEqual([
|
||||
['profile21', 'profile22'],
|
||||
{
|
||||
eventType: 'discover_field_usage',
|
||||
eventData: {
|
||||
eventName: 'dataTableSelection',
|
||||
fieldName: 'test',
|
||||
},
|
||||
},
|
||||
['profile1', 'profile2'],
|
||||
['profile21', 'profile22'],
|
||||
{
|
||||
eventType: 'discover_field_usage',
|
||||
eventData: {
|
||||
eventName: 'dataTableRemoval',
|
||||
fieldName: 'test',
|
||||
},
|
||||
},
|
||||
['profile1', 'profile2'],
|
||||
['profile21', 'profile22'],
|
||||
{
|
||||
eventType: 'discover_field_usage',
|
||||
eventData: {
|
||||
eventName: 'filterAddition',
|
||||
fieldName: 'test',
|
||||
filterOperation: '+',
|
||||
},
|
||||
},
|
||||
['profile1', 'profile2'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackContextualProfileResolvedEvent', () => {
|
||||
it('should track the event when a next contextual profile is resolved', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
scopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'discover_profile_resolved',
|
||||
{
|
||||
contextLevel: 'rootLevel',
|
||||
profileId: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
scopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.dataSourceLevel,
|
||||
profileId: 'data-source-test',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'discover_profile_resolved',
|
||||
{
|
||||
contextLevel: 'dataSourceLevel',
|
||||
profileId: 'data-source-test',
|
||||
}
|
||||
);
|
||||
|
||||
scopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.documentLevel,
|
||||
profileId: 'document-test',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'discover_profile_resolved',
|
||||
{
|
||||
contextLevel: 'documentLevel',
|
||||
profileId: 'document-test',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should not trigger duplicate requests', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
scopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
scopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
scopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test2',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should temporarily update the discoverEbtContext$ when a contextual profile is resolved in an inactive scoped manager', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
const anotherScopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
anotherScopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
const results: unknown[] = [];
|
||||
|
||||
discoverEbtContext$.pipe(skip(1)).subscribe(({ discoverProfiles }) => {
|
||||
results.push(discoverProfiles);
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(coreSetupMock.analytics, 'reportEvent')
|
||||
.mockImplementation((eventType, eventData) => {
|
||||
results.push({ eventType, eventData });
|
||||
});
|
||||
|
||||
anotherScopedManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test',
|
||||
});
|
||||
|
||||
expect(results).toEqual([
|
||||
['profile21', 'profile22'],
|
||||
{
|
||||
eventType: 'discover_profile_resolved',
|
||||
eventData: {
|
||||
contextLevel: 'rootLevel',
|
||||
profileId: 'test',
|
||||
},
|
||||
},
|
||||
['profile1', 'profile2'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackPerformanceEvent', () => {
|
||||
it('should track performance events', () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
scopedManager.setAsActiveManager();
|
||||
|
||||
jest.spyOn(window.performance, 'now').mockReturnValueOnce(250).mockReturnValueOnce(1000);
|
||||
|
||||
const tracker = scopedManager.trackPerformanceEvent('testEvent');
|
||||
tracker.reportEvent({ meta: { foo: 'bar' } });
|
||||
|
||||
expect(reportPerformanceMetricEvent).toHaveBeenCalledWith(coreSetupMock.analytics, {
|
||||
eventName: 'testEvent',
|
||||
duration: 750,
|
||||
meta: { foo: 'bar' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should temporarily update the discoverEbtContext$ when tracking performance events in an inactive scoped manager', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
|
||||
const scopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
const anotherScopedManager = discoverEBTContextManager.createScopedEBTManager();
|
||||
|
||||
scopedManager.setAsActiveManager();
|
||||
scopedManager.updateProfilesContextWith(dscProfiles);
|
||||
anotherScopedManager.updateProfilesContextWith(dscProfiles2);
|
||||
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
const results: unknown[] = [];
|
||||
|
||||
discoverEbtContext$.pipe(skip(1)).subscribe(({ discoverProfiles }) => {
|
||||
results.push(discoverProfiles);
|
||||
});
|
||||
|
||||
(reportPerformanceMetricEvent as jest.Mock).mockImplementation((_, eventData) => {
|
||||
results.push(eventData);
|
||||
});
|
||||
|
||||
jest.spyOn(window.performance, 'now').mockReturnValueOnce(250).mockReturnValueOnce(1000);
|
||||
|
||||
const tracker = anotherScopedManager.trackPerformanceEvent('testEvent');
|
||||
tracker.reportEvent({ meta: { foo: 'bar' } });
|
||||
|
||||
expect(results).toEqual([
|
||||
['profile21', 'profile22'],
|
||||
{
|
||||
eventName: 'testEvent',
|
||||
duration: 750,
|
||||
meta: { foo: 'bar' },
|
||||
},
|
||||
['profile1', 'profile2'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { BehaviorSubject } from 'rxjs';
|
||||
import { isEqual } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { CoreSetup } from '@kbn/core-lifecycle-browser';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type {
|
||||
DiscoverEBTContext,
|
||||
DiscoverEBTContextProps,
|
||||
ReportEvent,
|
||||
ReportPerformanceEvent,
|
||||
SetAsActiveManager,
|
||||
UpdateProfilesContextWith,
|
||||
} from './types';
|
||||
import { ScopedDiscoverEBTManager } from './scoped_discover_ebt_manager';
|
||||
|
||||
export class DiscoverEBTManager {
|
||||
private isCustomContextEnabled: boolean = false;
|
||||
private customContext$: DiscoverEBTContext | undefined;
|
||||
private activeScopedManagerId: string | undefined;
|
||||
private reportEvent: ReportEvent | undefined;
|
||||
private reportPerformanceEvent: ReportPerformanceEvent | undefined;
|
||||
|
||||
private updateProfilesContextWith: UpdateProfilesContextWith = (discoverProfiles) => {
|
||||
if (
|
||||
this.isCustomContextEnabled &&
|
||||
this.customContext$ &&
|
||||
!isEqual(this.customContext$.getValue().discoverProfiles, discoverProfiles)
|
||||
) {
|
||||
this.customContext$.next({
|
||||
discoverProfiles,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// https://docs.elastic.dev/telemetry/collection/event-based-telemetry
|
||||
public initialize({
|
||||
core,
|
||||
discoverEbtContext$,
|
||||
}: {
|
||||
core: CoreSetup;
|
||||
discoverEbtContext$: BehaviorSubject<DiscoverEBTContextProps>;
|
||||
}) {
|
||||
this.customContext$ = discoverEbtContext$;
|
||||
this.reportEvent = core.analytics.reportEvent;
|
||||
this.reportPerformanceEvent = (eventData) =>
|
||||
reportPerformanceMetricEvent(core.analytics, eventData);
|
||||
}
|
||||
|
||||
public onDiscoverAppMounted() {
|
||||
this.isCustomContextEnabled = true;
|
||||
}
|
||||
|
||||
public onDiscoverAppUnmounted() {
|
||||
this.updateProfilesContextWith([]);
|
||||
this.isCustomContextEnabled = false;
|
||||
this.activeScopedManagerId = undefined;
|
||||
}
|
||||
|
||||
public getProfilesContext() {
|
||||
return this.customContext$?.getValue()?.discoverProfiles;
|
||||
}
|
||||
|
||||
public createScopedEBTManager() {
|
||||
const scopedManagerId = uuidv4();
|
||||
let scopedDiscoverProfiles: string[] = [];
|
||||
|
||||
const withScopedContext =
|
||||
<T extends (...params: Parameters<T>) => void>(callback: T) =>
|
||||
(...params: Parameters<T>) => {
|
||||
const currentDiscoverProfiles = this.customContext$?.getValue().discoverProfiles ?? [];
|
||||
this.updateProfilesContextWith(scopedDiscoverProfiles);
|
||||
callback(...params);
|
||||
this.updateProfilesContextWith(currentDiscoverProfiles);
|
||||
};
|
||||
|
||||
const scopedReportEvent = this.reportEvent ? withScopedContext(this.reportEvent) : undefined;
|
||||
|
||||
const scopedReportPerformanceEvent = this.reportPerformanceEvent
|
||||
? withScopedContext(this.reportPerformanceEvent)
|
||||
: undefined;
|
||||
|
||||
const scopedUpdateProfilesContextWith: UpdateProfilesContextWith = (discoverProfiles) => {
|
||||
scopedDiscoverProfiles = discoverProfiles;
|
||||
if (this.activeScopedManagerId === scopedManagerId) {
|
||||
this.updateProfilesContextWith(discoverProfiles);
|
||||
}
|
||||
};
|
||||
|
||||
const scopedSetAsActiveManager: SetAsActiveManager = () => {
|
||||
this.activeScopedManagerId = scopedManagerId;
|
||||
this.updateProfilesContextWith(scopedDiscoverProfiles);
|
||||
};
|
||||
|
||||
return new ScopedDiscoverEBTManager(
|
||||
scopedReportEvent,
|
||||
scopedReportPerformanceEvent,
|
||||
scopedUpdateProfilesContextWith,
|
||||
scopedSetAsActiveManager
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
import type { CoreSetup } from '@kbn/core/public';
|
||||
import type { BehaviorSubject } from 'rxjs';
|
||||
import type { DiscoverStartPlugins } from '../types';
|
||||
import type { DiscoverEBTContextProps } from './discover_ebt_manager';
|
||||
import type { DiscoverEBTContextProps } from './types';
|
||||
|
||||
/**
|
||||
* Field usage events i.e. when a field is selected in the data table, removed from the data table, or a filter is added
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export {
|
||||
registerDiscoverEBTManagerAnalytics,
|
||||
FIELD_USAGE_EVENT_TYPE,
|
||||
FIELD_USAGE_EVENT_NAME,
|
||||
FIELD_USAGE_FIELD_NAME,
|
||||
FIELD_USAGE_FILTER_OPERATION,
|
||||
CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE,
|
||||
CONTEXTUAL_PROFILE_LEVEL,
|
||||
CONTEXTUAL_PROFILE_ID,
|
||||
} from './discover_ebt_manager_registrations';
|
||||
|
||||
export { DiscoverEBTManager } from './discover_ebt_manager';
|
||||
|
||||
export type { ScopedDiscoverEBTManager } from './scoped_discover_ebt_manager';
|
||||
|
||||
export type { DiscoverEBTContextProps, DiscoverEBTContext } from './types';
|
|
@ -7,12 +7,8 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { BehaviorSubject } from 'rxjs';
|
||||
import { isEqual } from 'lodash';
|
||||
import type { CoreSetup } from '@kbn/core-lifecycle-browser';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { ContextualProfileLevel } from '../context_awareness/profiles_manager';
|
||||
import type { PerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import {
|
||||
CONTEXTUAL_PROFILE_ID,
|
||||
CONTEXTUAL_PROFILE_LEVEL,
|
||||
|
@ -22,10 +18,17 @@ import {
|
|||
FIELD_USAGE_FIELD_NAME,
|
||||
FIELD_USAGE_FILTER_OPERATION,
|
||||
} from './discover_ebt_manager_registrations';
|
||||
import { ContextualProfileLevel } from '../context_awareness';
|
||||
import type {
|
||||
ReportEvent,
|
||||
ReportPerformanceEvent,
|
||||
SetAsActiveManager,
|
||||
UpdateProfilesContextWith,
|
||||
} from './types';
|
||||
|
||||
type FilterOperation = '+' | '-' | '_exists_';
|
||||
|
||||
export enum FieldUsageEventName {
|
||||
enum FieldUsageEventName {
|
||||
dataTableSelection = 'dataTableSelection',
|
||||
dataTableRemoval = 'dataTableRemoval',
|
||||
filterAddition = 'filterAddition',
|
||||
|
@ -41,90 +44,23 @@ interface ContextualProfileResolvedEventData {
|
|||
[CONTEXTUAL_PROFILE_ID]: string;
|
||||
}
|
||||
|
||||
export interface DiscoverEBTContextProps {
|
||||
discoverProfiles: string[]; // Discover Context Awareness Profiles
|
||||
}
|
||||
export type DiscoverEBTContext = BehaviorSubject<DiscoverEBTContextProps>;
|
||||
|
||||
export class DiscoverEBTManager {
|
||||
private isCustomContextEnabled: boolean = false;
|
||||
private customContext$: DiscoverEBTContext | undefined;
|
||||
private reportEvent: CoreSetup['analytics']['reportEvent'] | undefined;
|
||||
export class ScopedDiscoverEBTManager {
|
||||
private lastResolvedContextProfiles: {
|
||||
[ContextualProfileLevel.rootLevel]: string | undefined;
|
||||
[ContextualProfileLevel.dataSourceLevel]: string | undefined;
|
||||
[ContextualProfileLevel.documentLevel]: string | undefined;
|
||||
} = {
|
||||
[ContextualProfileLevel.rootLevel]: undefined,
|
||||
[ContextualProfileLevel.dataSourceLevel]: undefined,
|
||||
[ContextualProfileLevel.documentLevel]: undefined,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.lastResolvedContextProfiles = {
|
||||
[ContextualProfileLevel.rootLevel]: undefined,
|
||||
[ContextualProfileLevel.dataSourceLevel]: undefined,
|
||||
[ContextualProfileLevel.documentLevel]: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public trackPerformanceEvent(eventName: string) {
|
||||
return { reportEvent: () => {} };
|
||||
}
|
||||
|
||||
// https://docs.elastic.dev/telemetry/collection/event-based-telemetry
|
||||
public initialize({
|
||||
core,
|
||||
discoverEbtContext$,
|
||||
}: {
|
||||
core: CoreSetup;
|
||||
discoverEbtContext$: BehaviorSubject<DiscoverEBTContextProps>;
|
||||
}) {
|
||||
this.customContext$ = discoverEbtContext$;
|
||||
this.reportEvent = core.analytics.reportEvent;
|
||||
this.trackPerformanceEvent = (eventName: string) => {
|
||||
const startTime = window.performance.now();
|
||||
let reported = false;
|
||||
|
||||
return {
|
||||
reportEvent: () => {
|
||||
if (reported) return;
|
||||
reported = true;
|
||||
const duration = window.performance.now() - startTime;
|
||||
reportPerformanceMetricEvent(core.analytics, {
|
||||
eventName,
|
||||
duration,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
public onDiscoverAppMounted() {
|
||||
this.isCustomContextEnabled = true;
|
||||
}
|
||||
|
||||
public onDiscoverAppUnmounted() {
|
||||
this.updateProfilesContextWith([]);
|
||||
this.isCustomContextEnabled = false;
|
||||
this.lastResolvedContextProfiles = {
|
||||
[ContextualProfileLevel.rootLevel]: undefined,
|
||||
[ContextualProfileLevel.dataSourceLevel]: undefined,
|
||||
[ContextualProfileLevel.documentLevel]: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public updateProfilesContextWith(discoverProfiles: DiscoverEBTContextProps['discoverProfiles']) {
|
||||
if (
|
||||
this.isCustomContextEnabled &&
|
||||
this.customContext$ &&
|
||||
!isEqual(this.customContext$.getValue().discoverProfiles, discoverProfiles)
|
||||
) {
|
||||
this.customContext$.next({
|
||||
discoverProfiles,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getProfilesContext() {
|
||||
return this.customContext$?.getValue()?.discoverProfiles;
|
||||
}
|
||||
constructor(
|
||||
private readonly reportEvent: ReportEvent | undefined,
|
||||
private readonly reportPerformanceEvent: ReportPerformanceEvent | undefined,
|
||||
public readonly updateProfilesContextWith: UpdateProfilesContextWith,
|
||||
public readonly setAsActiveManager: SetAsActiveManager
|
||||
) {}
|
||||
|
||||
private async trackFieldUsageEvent({
|
||||
eventName,
|
||||
|
@ -235,4 +171,26 @@ export class DiscoverEBTManager {
|
|||
|
||||
this.reportEvent(CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE, eventData);
|
||||
}
|
||||
|
||||
public trackPerformanceEvent(eventName: string) {
|
||||
const startTime = window.performance.now();
|
||||
let reported = false;
|
||||
|
||||
return {
|
||||
reportEvent: (eventData?: Omit<PerformanceMetricEvent, 'eventName' | 'duration'>) => {
|
||||
if (reported || !this.reportPerformanceEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
reported = true;
|
||||
const duration = window.performance.now() - startTime;
|
||||
|
||||
this.reportPerformanceEvent({
|
||||
...eventData,
|
||||
eventName,
|
||||
duration,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { CoreSetup } from '@kbn/core/public';
|
||||
import type { PerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export interface DiscoverEBTContextProps {
|
||||
discoverProfiles: string[]; // Discover Context Awareness Profiles
|
||||
}
|
||||
export type DiscoverEBTContext = BehaviorSubject<DiscoverEBTContextProps>;
|
||||
|
||||
export type ReportEvent = CoreSetup['analytics']['reportEvent'];
|
||||
|
||||
export type ReportPerformanceEvent = (eventData: PerformanceMetricEvent) => void;
|
||||
|
||||
export type UpdateProfilesContextWith = (
|
||||
discoverProfiles: DiscoverEBTContextProps['discoverProfiles']
|
||||
) => void;
|
||||
|
||||
export type SetAsActiveManager = () => void;
|
|
@ -255,8 +255,11 @@ describe('saved search embeddable', () => {
|
|||
});
|
||||
|
||||
it('should resolve data source profile when fetching', async () => {
|
||||
const scopedProfilesManager =
|
||||
discoverServiceMock.profilesManager.createScopedProfilesManager();
|
||||
const scopedProfilesManager = discoverServiceMock.profilesManager.createScopedProfilesManager(
|
||||
{
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
}
|
||||
);
|
||||
const resolveDataSourceProfileSpy = jest.spyOn(
|
||||
scopedProfilesManager,
|
||||
'resolveDataSourceProfile'
|
||||
|
|
|
@ -40,7 +40,8 @@ import { initializeFetch, isEsqlMode } from './initialize_fetch';
|
|||
import { initializeSearchEmbeddableApi } from './initialize_search_embeddable_api';
|
||||
import type { SearchEmbeddableApi, SearchEmbeddableSerializedState } from './types';
|
||||
import { deserializeState, serializeState } from './utils/serialization_utils';
|
||||
import { BaseAppWrapper, ScopedProfilesManagerProvider } from '../context_awareness';
|
||||
import { BaseAppWrapper } from '../context_awareness';
|
||||
import { ScopedServicesProvider } from '../components/scoped_services_provider';
|
||||
|
||||
export const getSearchEmbeddableFactory = ({
|
||||
startServices,
|
||||
|
@ -73,7 +74,10 @@ export const getSearchEmbeddableFactory = ({
|
|||
solutionNavId,
|
||||
});
|
||||
const AppWrapper = getRenderAppWrapper?.(BaseAppWrapper) ?? BaseAppWrapper;
|
||||
const scopedProfilesManager = discoverServices.profilesManager.createScopedProfilesManager();
|
||||
const scopedEbtManager = discoverServices.ebtManager.createScopedEBTManager();
|
||||
const scopedProfilesManager = discoverServices.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager,
|
||||
});
|
||||
|
||||
/** Specific by-reference state */
|
||||
const savedObjectId$ = new BehaviorSubject<string | undefined>(runtimeState?.savedObjectId);
|
||||
|
@ -315,7 +319,10 @@ export const getSearchEmbeddableFactory = ({
|
|||
return (
|
||||
<KibanaRenderContextProvider {...discoverServices.core}>
|
||||
<KibanaContextProvider services={discoverServices}>
|
||||
<ScopedProfilesManagerProvider scopedProfilesManager={scopedProfilesManager}>
|
||||
<ScopedServicesProvider
|
||||
scopedProfilesManager={scopedProfilesManager}
|
||||
scopedEBTManager={scopedEbtManager}
|
||||
>
|
||||
<AppWrapper>
|
||||
{renderAsFieldStatsTable ? (
|
||||
<SearchEmbeddablFieldStatsTableComponent
|
||||
|
@ -353,7 +360,7 @@ export const getSearchEmbeddableFactory = ({
|
|||
</CellActionsProvider>
|
||||
)}
|
||||
</AppWrapper>
|
||||
</ScopedProfilesManagerProvider>
|
||||
</ScopedServicesProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaRenderContextProvider>
|
||||
);
|
||||
|
|
|
@ -42,7 +42,9 @@ describe('initialize fetch', () => {
|
|||
api: mockedApi,
|
||||
stateManager,
|
||||
discoverServices: discoverServiceMock,
|
||||
scopedProfilesManager: discoverServiceMock.profilesManager.createScopedProfilesManager(),
|
||||
scopedProfilesManager: discoverServiceMock.profilesManager.createScopedProfilesManager({
|
||||
scopedEbtManager: discoverServiceMock.ebtManager.createScopedEBTManager(),
|
||||
}),
|
||||
...setters,
|
||||
});
|
||||
await waitOneTick();
|
||||
|
|
|
@ -57,13 +57,10 @@ import type {
|
|||
DiscoverStartPlugins,
|
||||
} from './types';
|
||||
import { DISCOVER_CELL_ACTIONS_TRIGGER } from './context_awareness/types';
|
||||
import type {
|
||||
DiscoverEBTContextProps,
|
||||
DiscoverEBTManager,
|
||||
} from './plugin_imports/discover_ebt_manager';
|
||||
import type { DiscoverEBTContextProps, DiscoverEBTManager } from './ebt_manager';
|
||||
import { registerDiscoverEBTManagerAnalytics } from './ebt_manager/discover_ebt_manager_registrations';
|
||||
import type { ProfilesManager } from './context_awareness';
|
||||
import { forwardLegacyUrls } from './plugin_imports/forward_legacy_urls';
|
||||
import { registerDiscoverEBTManagerAnalytics } from './plugin_imports/discover_ebt_manager_registrations';
|
||||
|
||||
/**
|
||||
* Contains Discover, one of the oldest parts of Kibana
|
||||
|
@ -262,7 +259,7 @@ export class DiscoverPlugin
|
|||
|
||||
const getDiscoverServicesInternal = async () => {
|
||||
const ebtManager = await getEmptyEbtManager();
|
||||
const { profilesManager } = await this.createProfileServices(ebtManager);
|
||||
const { profilesManager } = await this.createProfileServices();
|
||||
return this.getDiscoverServices({ core, plugins, profilesManager, ebtManager });
|
||||
};
|
||||
|
||||
|
@ -280,7 +277,7 @@ export class DiscoverPlugin
|
|||
}
|
||||
}
|
||||
|
||||
private async createProfileServices(ebtManager: DiscoverEBTManager) {
|
||||
private async createProfileServices() {
|
||||
const {
|
||||
RootProfileService,
|
||||
DataSourceProfileService,
|
||||
|
@ -294,8 +291,7 @@ export class DiscoverPlugin
|
|||
const profilesManager = new ProfilesManager(
|
||||
rootProfileService,
|
||||
dataSourceProfileService,
|
||||
documentProfileService,
|
||||
ebtManager
|
||||
documentProfileService
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -324,7 +320,7 @@ export class DiscoverPlugin
|
|||
dataSourceProfileService,
|
||||
documentProfileService,
|
||||
profilesManager,
|
||||
} = await this.createProfileServices(ebtManager);
|
||||
} = await this.createProfileServices();
|
||||
const services = await this.getDiscoverServices({
|
||||
core,
|
||||
plugins,
|
||||
|
|
|
@ -1,353 +0,0 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { type DiscoverEBTContextProps, DiscoverEBTManager } from './discover_ebt_manager';
|
||||
import { registerDiscoverEBTManagerAnalytics } from './discover_ebt_manager_registrations';
|
||||
import { ContextualProfileLevel } from '../context_awareness/profiles_manager';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
describe('DiscoverEBTManager', () => {
|
||||
let discoverEBTContextManager: DiscoverEBTManager;
|
||||
let discoverEbtContext$: BehaviorSubject<DiscoverEBTContextProps>;
|
||||
|
||||
const coreSetupMock = coreMock.createSetup();
|
||||
|
||||
const fieldsMetadata = {
|
||||
getClient: jest.fn().mockResolvedValue({
|
||||
find: jest.fn().mockResolvedValue({
|
||||
fields: {
|
||||
test: {
|
||||
short: 'test',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
} as unknown as FieldsMetadataPublicStart;
|
||||
|
||||
beforeEach(() => {
|
||||
discoverEBTContextManager = new DiscoverEBTManager();
|
||||
discoverEbtContext$ = new BehaviorSubject<DiscoverEBTContextProps>({
|
||||
discoverProfiles: [],
|
||||
});
|
||||
(coreSetupMock.analytics.reportEvent as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register the context provider and custom events', () => {
|
||||
registerDiscoverEBTManagerAnalytics(coreSetupMock, discoverEbtContext$);
|
||||
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.registerContextProvider).toHaveBeenCalledWith({
|
||||
name: 'discover_context',
|
||||
context$: expect.any(BehaviorSubject),
|
||||
schema: {
|
||||
discoverProfiles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'List of active Discover context awareness profiles',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.registerEventType).toHaveBeenCalledWith({
|
||||
eventType: 'discover_field_usage',
|
||||
schema: {
|
||||
eventName: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description:
|
||||
'The name of the event that is tracked in the metrics i.e. dataTableSelection, dataTableRemoval',
|
||||
},
|
||||
},
|
||||
fieldName: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: "Field name if it's a part of ECS schema",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
filterOperation: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: "Operation type when a filter is added i.e. '+', '-', '_exists_'",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateProfilesWith', () => {
|
||||
it('should update the profiles with the provided props', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles2);
|
||||
});
|
||||
|
||||
it('should not update the profiles if profile list did not change', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles2);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
});
|
||||
|
||||
it('should not update the profiles if not enabled yet', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not update the profiles after resetting unless enabled again', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
discoverEBTContextManager.onDiscoverAppUnmounted();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackFieldUsageEvent', () => {
|
||||
it('should track the field usage when a field is added to the table', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
await discoverEBTContextManager.trackDataTableSelection({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableSelection',
|
||||
fieldName: 'test',
|
||||
});
|
||||
|
||||
await discoverEBTContextManager.trackDataTableSelection({
|
||||
fieldName: 'test2',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenLastCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableSelection', // non-ECS fields would not be included in properties
|
||||
});
|
||||
});
|
||||
|
||||
it('should track the field usage when a field is removed from the table', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
await discoverEBTContextManager.trackDataTableRemoval({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableRemoval',
|
||||
fieldName: 'test',
|
||||
});
|
||||
|
||||
await discoverEBTContextManager.trackDataTableRemoval({
|
||||
fieldName: 'test2',
|
||||
fieldsMetadata,
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenLastCalledWith('discover_field_usage', {
|
||||
eventName: 'dataTableRemoval', // non-ECS fields would not be included in properties
|
||||
});
|
||||
});
|
||||
|
||||
it('should track the field usage when a filter is created', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
await discoverEBTContextManager.trackFilterAddition({
|
||||
fieldName: 'test',
|
||||
fieldsMetadata,
|
||||
filterOperation: '+',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledWith('discover_field_usage', {
|
||||
eventName: 'filterAddition',
|
||||
fieldName: 'test',
|
||||
filterOperation: '+',
|
||||
});
|
||||
|
||||
await discoverEBTContextManager.trackFilterAddition({
|
||||
fieldName: 'test2',
|
||||
fieldsMetadata,
|
||||
filterOperation: '_exists_',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenLastCalledWith('discover_field_usage', {
|
||||
eventName: 'filterAddition', // non-ECS fields would not be included in properties
|
||||
filterOperation: '_exists_',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackContextualProfileResolvedEvent', () => {
|
||||
it('should track the event when a next contextual profile is resolved', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'discover_profile_resolved',
|
||||
{
|
||||
contextLevel: 'rootLevel',
|
||||
profileId: 'test',
|
||||
}
|
||||
);
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.dataSourceLevel,
|
||||
profileId: 'data-source-test',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'discover_profile_resolved',
|
||||
{
|
||||
contextLevel: 'dataSourceLevel',
|
||||
profileId: 'data-source-test',
|
||||
}
|
||||
);
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.documentLevel,
|
||||
profileId: 'document-test',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'discover_profile_resolved',
|
||||
{
|
||||
contextLevel: 'documentLevel',
|
||||
profileId: 'document-test',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should not trigger duplicate requests', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test2',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should trigger similar requests after remount', async () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
discoverEbtContext$,
|
||||
});
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
discoverEBTContextManager.onDiscoverAppUnmounted();
|
||||
discoverEBTContextManager.onDiscoverAppMounted();
|
||||
|
||||
discoverEBTContextManager.trackContextualProfileResolvedEvent({
|
||||
contextLevel: ContextualProfileLevel.rootLevel,
|
||||
profileId: 'test1',
|
||||
});
|
||||
|
||||
expect(coreSetupMock.analytics.reportEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
export { HistoryService } from './history_service';
|
||||
export { DiscoverEBTManager } from './discover_ebt_manager';
|
||||
export { DiscoverEBTManager } from '../ebt_manager/discover_ebt_manager';
|
||||
export { RootProfileService } from '../context_awareness/profiles/root_profile';
|
||||
export { DataSourceProfileService } from '../context_awareness/profiles/data_source_profile';
|
||||
export { DocumentProfileService } from '../context_awareness/profiles/document_profile';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue