mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[OneDiscover] Add EBT event to track field usage (#193996)
- Closes https://github.com/elastic/kibana/issues/186156 - Closes https://github.com/elastic/kibana/issues/189454 ## Summary This PR adds new EBT event type `discover_field_usage` which we use for tracking adding and removing grid columns and adding filters via +/-/exists buttons. Properties of the added events consist of: `eventType`: `dataTableSelection`, `dataTableRemoval`, or `filterAddition` `fieldName`: name of the field if it's from ECS schema `filterOperation`: `+`, `-`, or `_exists_` <img width="1002" alt="Screenshot 2024-09-25 at 17 51 27" src="https://github.com/user-attachments/assets/b3f3fb69-55e1-43b2-9683-a6d8884f56fe"> ## Testing Enable "Usage collection" global setting. Navigate to Discover and observe `kibana-browser` requests in Network tab. ### Checklist - [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
This commit is contained in:
parent
5def848d2c
commit
7aa64b6ed5
17 changed files with 946 additions and 337 deletions
|
@ -45,6 +45,7 @@ import { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
|||
import { urlTrackerMock } from './url_tracker.mock';
|
||||
import { createElement } from 'react';
|
||||
import { createContextAwarenessMocks } from '../context_awareness/__mocks__';
|
||||
import { DiscoverEBTManager } from '../services/discover_ebt_manager';
|
||||
|
||||
export function createDiscoverServicesMock(): DiscoverServices {
|
||||
const dataPlugin = dataPluginMock.createStartContract();
|
||||
|
@ -245,6 +246,7 @@ export function createDiscoverServicesMock(): DiscoverServices {
|
|||
singleDocLocator: { getRedirectUrl: jest.fn(() => '') },
|
||||
urlTracker: urlTrackerMock,
|
||||
profilesManager: profilesManagerMock,
|
||||
ebtManager: new DiscoverEBTManager(),
|
||||
setHeaderActionMenu: jest.fn(),
|
||||
} as unknown as DiscoverServices;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ describe('ContextApp test', () => {
|
|||
contextLocator: { getRedirectUrl: jest.fn(() => '') },
|
||||
singleDocLocator: { getRedirectUrl: jest.fn(() => '') },
|
||||
profilesManager: discoverServices.profilesManager,
|
||||
ebtManager: discoverServices.ebtManager,
|
||||
timefilter: discoverServices.timefilter,
|
||||
uiActions: discoverServices.uiActions,
|
||||
} as unknown as DiscoverServices;
|
||||
|
|
|
@ -56,6 +56,8 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
navigation,
|
||||
filterManager,
|
||||
core,
|
||||
ebtManager,
|
||||
fieldsMetadata,
|
||||
} = services;
|
||||
|
||||
const isLegacy = useMemo(() => uiSettings.get(DOC_TABLE_LEGACY), [uiSettings]);
|
||||
|
@ -199,15 +201,36 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
);
|
||||
|
||||
const addFilter = useCallback(
|
||||
async (field: DataViewField | string, values: unknown, operation: string) => {
|
||||
async (field: DataViewField | string, values: unknown, operation: '+' | '-') => {
|
||||
const newFilters = generateFilters(filterManager, field, values, operation, dataView);
|
||||
filterManager.addFilters(newFilters);
|
||||
if (dataViews) {
|
||||
const fieldName = typeof field === 'string' ? field : field.name;
|
||||
await popularizeField(dataView, fieldName, dataViews, capabilities);
|
||||
void ebtManager.trackFilterAddition({
|
||||
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
|
||||
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
|
||||
fieldsMetadata,
|
||||
});
|
||||
}
|
||||
},
|
||||
[filterManager, dataViews, dataView, capabilities]
|
||||
[filterManager, dataViews, dataView, capabilities, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onAddColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onAddColumn(columnName);
|
||||
void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onAddColumn, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onRemoveColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onRemoveColumn(columnName);
|
||||
void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onRemoveColumn, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const TopNavMenu = navigation.ui.AggregateQueryTopNavMenu;
|
||||
|
@ -271,8 +294,8 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
isLegacy={isLegacy}
|
||||
columns={columns}
|
||||
grid={appState.grid}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onAddColumn={onAddColumnWithTracking}
|
||||
onRemoveColumn={onRemoveColumnWithTracking}
|
||||
onSetColumns={onSetColumns}
|
||||
predecessorCount={appState.predecessorCount}
|
||||
successorCount={appState.successorCount}
|
||||
|
|
|
@ -117,7 +117,7 @@ function DiscoverDocumentsComponent({
|
|||
const services = useDiscoverServices();
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const { dataViews, capabilities, uiSettings, uiActions } = services;
|
||||
const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services;
|
||||
const [
|
||||
dataSource,
|
||||
query,
|
||||
|
@ -200,6 +200,22 @@ function DiscoverDocumentsComponent({
|
|||
settings: grid,
|
||||
});
|
||||
|
||||
const onAddColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onAddColumn(columnName);
|
||||
void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onAddColumn, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onRemoveColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onRemoveColumn(columnName);
|
||||
void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onRemoveColumn, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const setExpandedDoc = useCallback(
|
||||
(doc: DataTableRecord | undefined) => {
|
||||
stateContainer.internalState.transitions.setExpandedDoc(doc);
|
||||
|
@ -299,14 +315,22 @@ function DiscoverDocumentsComponent({
|
|||
columnsMeta={customColumnsMeta}
|
||||
savedSearchId={savedSearch.id}
|
||||
onFilter={onAddFilter}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumnWithTracking}
|
||||
onAddColumn={onAddColumnWithTracking}
|
||||
onClose={() => setExpandedDoc(undefined)}
|
||||
setExpandedDoc={setExpandedDoc}
|
||||
query={query}
|
||||
/>
|
||||
),
|
||||
[dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc]
|
||||
[
|
||||
dataView,
|
||||
onAddColumnWithTracking,
|
||||
onAddFilter,
|
||||
onRemoveColumnWithTracking,
|
||||
query,
|
||||
savedSearch.id,
|
||||
setExpandedDoc,
|
||||
]
|
||||
);
|
||||
|
||||
const configRowHeight = uiSettings.get(ROW_HEIGHT_OPTION);
|
||||
|
|
|
@ -78,6 +78,8 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
spaces,
|
||||
observabilityAIAssistant,
|
||||
dataVisualizer: dataVisualizerService,
|
||||
ebtManager,
|
||||
fieldsMetadata,
|
||||
} = useDiscoverServices();
|
||||
const pageBackgroundColor = useEuiBackgroundColor('plain');
|
||||
const globalQueryState = data.query.getState();
|
||||
|
@ -154,6 +156,22 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
settings: grid,
|
||||
});
|
||||
|
||||
const onAddColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onAddColumn(columnName);
|
||||
void ebtManager.trackDataTableSelection({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onAddColumn, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onRemoveColumnWithTracking = useCallback(
|
||||
(columnName: string) => {
|
||||
onRemoveColumn(columnName);
|
||||
void ebtManager.trackDataTableRemoval({ fieldName: columnName, fieldsMetadata });
|
||||
},
|
||||
[onRemoveColumn, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
// The assistant is getting the state from the url correctly
|
||||
// expect from the index pattern where we have only the dataview id
|
||||
useEffect(() => {
|
||||
|
@ -175,9 +193,14 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'filter_added');
|
||||
}
|
||||
void ebtManager.trackFilterAddition({
|
||||
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
|
||||
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
|
||||
fieldsMetadata,
|
||||
});
|
||||
return filterManager.addFilters(newFilters);
|
||||
},
|
||||
[filterManager, dataView, dataViews, trackUiMetric, capabilities]
|
||||
[filterManager, dataView, dataViews, trackUiMetric, capabilities, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const getOperator = (fieldName: string, values: unknown, operation: '+' | '-') => {
|
||||
|
@ -222,8 +245,13 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
if (trackUiMetric) {
|
||||
trackUiMetric(METRIC_TYPE.CLICK, 'esql_filter_added');
|
||||
}
|
||||
void ebtManager.trackFilterAddition({
|
||||
fieldName: fieldName === '_exists_' ? String(values) : fieldName,
|
||||
filterOperation: fieldName === '_exists_' ? '_exists_' : operation,
|
||||
fieldsMetadata,
|
||||
});
|
||||
},
|
||||
[data.query.queryString, query, trackUiMetric]
|
||||
[data.query.queryString, query, trackUiMetric, ebtManager, fieldsMetadata]
|
||||
);
|
||||
|
||||
const onFilter = isEsqlMode ? onPopulateWhereClause : onAddFilter;
|
||||
|
@ -274,8 +302,8 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
return () => onAddColumn(draggingFieldName);
|
||||
}, [onAddColumn, draggingFieldName, currentColumns]);
|
||||
return () => onAddColumnWithTracking(draggingFieldName);
|
||||
}, [onAddColumnWithTracking, draggingFieldName, currentColumns]);
|
||||
|
||||
const [sidebarToggleState$] = useState<BehaviorSubject<SidebarToggleState>>(
|
||||
() => new BehaviorSubject<SidebarToggleState>({ isCollapsed: false, toggle: () => {} })
|
||||
|
@ -396,10 +424,10 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
sidebarPanel={
|
||||
<SidebarMemoized
|
||||
documents$={stateContainer.dataState.data$.documents$}
|
||||
onAddField={onAddColumn}
|
||||
onAddField={onAddColumnWithTracking}
|
||||
onRemoveField={onRemoveColumnWithTracking}
|
||||
columns={currentColumns}
|
||||
onAddFilter={onFilter}
|
||||
onRemoveField={onRemoveColumn}
|
||||
onChangeDataView={stateContainer.actions.onChangeDataView}
|
||||
selectedDataView={dataView}
|
||||
trackUiMetric={trackUiMetric}
|
||||
|
|
|
@ -64,7 +64,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 { DiscoverEBTContextManager } from './services/discover_ebt_context_manager';
|
||||
import type { DiscoverEBTManager } from './services/discover_ebt_manager';
|
||||
|
||||
/**
|
||||
* Location state of internal Discover history instance
|
||||
|
@ -132,7 +132,7 @@ export interface DiscoverServices {
|
|||
noDataPage?: NoDataPagePluginStart;
|
||||
observabilityAIAssistant?: ObservabilityAIAssistantPublicStart;
|
||||
profilesManager: ProfilesManager;
|
||||
ebtContextManager: DiscoverEBTContextManager;
|
||||
ebtManager: DiscoverEBTManager;
|
||||
fieldsMetadata?: FieldsMetadataPublicStart;
|
||||
logsDataAccess?: LogsDataAccessPluginStart;
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ export const buildServices = memoize(
|
|||
scopedHistory,
|
||||
urlTracker,
|
||||
profilesManager,
|
||||
ebtContextManager,
|
||||
ebtManager,
|
||||
setHeaderActionMenu = noop,
|
||||
}: {
|
||||
core: CoreStart;
|
||||
|
@ -162,7 +162,7 @@ export const buildServices = memoize(
|
|||
scopedHistory?: ScopedHistory;
|
||||
urlTracker: UrlTracker;
|
||||
profilesManager: ProfilesManager;
|
||||
ebtContextManager: DiscoverEBTContextManager;
|
||||
ebtManager: DiscoverEBTManager;
|
||||
setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu'];
|
||||
}): DiscoverServices => {
|
||||
const { usageCollection } = plugins;
|
||||
|
@ -223,7 +223,7 @@ export const buildServices = memoize(
|
|||
noDataPage: plugins.noDataPage,
|
||||
observabilityAIAssistant: plugins.observabilityAIAssistant,
|
||||
profilesManager,
|
||||
ebtContextManager,
|
||||
ebtManager,
|
||||
fieldsMetadata: plugins.fieldsMetadata,
|
||||
logsDataAccess: plugins.logsDataAccess,
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from '../profiles';
|
||||
import { ProfileProviderServices } from '../profile_providers/profile_provider_services';
|
||||
import { ProfilesManager } from '../profiles_manager';
|
||||
import { DiscoverEBTContextManager } from '../../services/discover_ebt_context_manager';
|
||||
import { DiscoverEBTManager } from '../../services/discover_ebt_manager';
|
||||
import { createLogsContextServiceMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
|
||||
export const createContextAwarenessMocks = ({
|
||||
|
@ -152,12 +152,12 @@ export const createContextAwarenessMocks = ({
|
|||
documentProfileServiceMock.registerProvider(documentProfileProviderMock);
|
||||
}
|
||||
|
||||
const ebtContextManagerMock = new DiscoverEBTContextManager();
|
||||
const ebtManagerMock = new DiscoverEBTManager();
|
||||
const profilesManagerMock = new ProfilesManager(
|
||||
rootProfileServiceMock,
|
||||
dataSourceProfileServiceMock,
|
||||
documentProfileServiceMock,
|
||||
ebtContextManagerMock
|
||||
ebtManagerMock
|
||||
);
|
||||
|
||||
const profileProviderServices = createProfileProviderServicesMock();
|
||||
|
@ -173,7 +173,7 @@ export const createContextAwarenessMocks = ({
|
|||
contextRecordMock2,
|
||||
profilesManagerMock,
|
||||
profileProviderServices,
|
||||
ebtContextManagerMock,
|
||||
ebtManagerMock,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('ProfilesManager', () => {
|
|||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mocks = createContextAwarenessMocks();
|
||||
jest.spyOn(mocks.ebtContextManagerMock, 'updateProfilesContextWith');
|
||||
jest.spyOn(mocks.ebtManagerMock, 'updateProfilesContextWith');
|
||||
});
|
||||
|
||||
it('should return default profiles', () => {
|
||||
|
@ -62,7 +62,7 @@ describe('ProfilesManager', () => {
|
|||
mocks.documentProfileProviderMock.profile,
|
||||
]);
|
||||
|
||||
expect(mocks.ebtContextManagerMock.updateProfilesContextWith).toHaveBeenCalledWith([
|
||||
expect(mocks.ebtManagerMock.updateProfilesContextWith).toHaveBeenCalledWith([
|
||||
'root-profile',
|
||||
'data-source-profile',
|
||||
]);
|
||||
|
|
|
@ -25,7 +25,7 @@ import type {
|
|||
DocumentContext,
|
||||
} from './profiles';
|
||||
import type { ContextWithProfileId } from './profile_service';
|
||||
import { DiscoverEBTContextManager } from '../services/discover_ebt_context_manager';
|
||||
import { DiscoverEBTManager } from '../services/discover_ebt_manager';
|
||||
|
||||
interface SerializedRootProfileParams {
|
||||
solutionNavId: RootProfileProviderParams['solutionNavId'];
|
||||
|
@ -53,7 +53,7 @@ export interface GetProfilesOptions {
|
|||
export class ProfilesManager {
|
||||
private readonly rootContext$: BehaviorSubject<ContextWithProfileId<RootContext>>;
|
||||
private readonly dataSourceContext$: BehaviorSubject<ContextWithProfileId<DataSourceContext>>;
|
||||
private readonly ebtContextManager: DiscoverEBTContextManager;
|
||||
private readonly ebtManager: DiscoverEBTManager;
|
||||
|
||||
private prevRootProfileParams?: SerializedRootProfileParams;
|
||||
private prevDataSourceProfileParams?: SerializedDataSourceProfileParams;
|
||||
|
@ -64,11 +64,11 @@ export class ProfilesManager {
|
|||
private readonly rootProfileService: RootProfileService,
|
||||
private readonly dataSourceProfileService: DataSourceProfileService,
|
||||
private readonly documentProfileService: DocumentProfileService,
|
||||
ebtContextManager: DiscoverEBTContextManager
|
||||
ebtManager: DiscoverEBTManager
|
||||
) {
|
||||
this.rootContext$ = new BehaviorSubject(rootProfileService.defaultContext);
|
||||
this.dataSourceContext$ = new BehaviorSubject(dataSourceProfileService.defaultContext);
|
||||
this.ebtContextManager = ebtContextManager;
|
||||
this.ebtManager = ebtManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -206,7 +206,7 @@ export class ProfilesManager {
|
|||
private trackActiveProfiles(rootContextProfileId: string, dataSourceContextProfileId: string) {
|
||||
const dscProfiles = [rootContextProfileId, dataSourceContextProfileId];
|
||||
|
||||
this.ebtContextManager.updateProfilesContextWith(dscProfiles);
|
||||
this.ebtManager.updateProfilesContextWith(dscProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ import { RootProfileService } from './context_awareness/profiles/root_profile';
|
|||
import { DataSourceProfileService } from './context_awareness/profiles/data_source_profile';
|
||||
import { DocumentProfileService } from './context_awareness/profiles/document_profile';
|
||||
import { ProfilesManager } from './context_awareness/profiles_manager';
|
||||
import { DiscoverEBTContextManager } from './services/discover_ebt_context_manager';
|
||||
import { DiscoverEBTManager } from './services/discover_ebt_manager';
|
||||
|
||||
/**
|
||||
* Contains Discover, one of the oldest parts of Kibana
|
||||
|
@ -149,8 +149,12 @@ export class DiscoverPlugin
|
|||
this.urlTracker = { setTrackedUrl, restorePreviousUrl, setTrackingEnabled };
|
||||
this.stopUrlTracking = stopUrlTracker;
|
||||
|
||||
const ebtContextManager = new DiscoverEBTContextManager();
|
||||
ebtContextManager.initialize({ core });
|
||||
const ebtManager = new DiscoverEBTManager();
|
||||
ebtManager.initialize({
|
||||
core,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: true,
|
||||
});
|
||||
|
||||
core.application.register({
|
||||
id: PLUGIN_ID,
|
||||
|
@ -176,7 +180,7 @@ export class DiscoverPlugin
|
|||
window.dispatchEvent(new HashChangeEvent('hashchange'));
|
||||
});
|
||||
|
||||
ebtContextManager.enable();
|
||||
ebtManager.enableContext();
|
||||
|
||||
const services = buildServices({
|
||||
core: coreStart,
|
||||
|
@ -188,12 +192,12 @@ export class DiscoverPlugin
|
|||
history: this.historyService.getHistory(),
|
||||
scopedHistory: this.scopedHistory,
|
||||
urlTracker: this.urlTracker!,
|
||||
profilesManager: await this.createProfilesManager(
|
||||
coreStart,
|
||||
discoverStartPlugins,
|
||||
ebtContextManager
|
||||
),
|
||||
ebtContextManager,
|
||||
profilesManager: await this.createProfilesManager({
|
||||
core: coreStart,
|
||||
plugins: discoverStartPlugins,
|
||||
ebtManager,
|
||||
}),
|
||||
ebtManager,
|
||||
setHeaderActionMenu: params.setHeaderActionMenu,
|
||||
});
|
||||
|
||||
|
@ -226,7 +230,7 @@ export class DiscoverPlugin
|
|||
});
|
||||
|
||||
return () => {
|
||||
ebtContextManager.disableAndReset();
|
||||
ebtManager.disableAndResetContext();
|
||||
unlistenParentHistory();
|
||||
unmount();
|
||||
appUnMounted();
|
||||
|
@ -296,11 +300,12 @@ export class DiscoverPlugin
|
|||
}
|
||||
|
||||
const getDiscoverServicesInternal = () => {
|
||||
const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover
|
||||
return this.getDiscoverServices(
|
||||
core,
|
||||
plugins,
|
||||
this.createEmptyProfilesManager(),
|
||||
new DiscoverEBTContextManager() // it's not enabled outside of Discover
|
||||
this.createEmptyProfilesManager({ ebtManager }),
|
||||
ebtManager
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -326,11 +331,15 @@ export class DiscoverPlugin
|
|||
return { rootProfileService, dataSourceProfileService, documentProfileService };
|
||||
}
|
||||
|
||||
private createProfilesManager = async (
|
||||
core: CoreStart,
|
||||
plugins: DiscoverStartPlugins,
|
||||
ebtContextManager: DiscoverEBTContextManager
|
||||
) => {
|
||||
private async createProfilesManager({
|
||||
core,
|
||||
plugins,
|
||||
ebtManager,
|
||||
}: {
|
||||
core: CoreStart;
|
||||
plugins: DiscoverStartPlugins;
|
||||
ebtManager: DiscoverEBTManager;
|
||||
}) {
|
||||
const { registerProfileProviders } = await import('./context_awareness/profile_providers');
|
||||
const { rootProfileService, dataSourceProfileService, documentProfileService } =
|
||||
this.createProfileServices();
|
||||
|
@ -341,7 +350,7 @@ export class DiscoverPlugin
|
|||
rootProfileService,
|
||||
dataSourceProfileService,
|
||||
documentProfileService,
|
||||
ebtContextManager
|
||||
ebtManager
|
||||
);
|
||||
|
||||
await registerProfileProviders({
|
||||
|
@ -349,21 +358,18 @@ export class DiscoverPlugin
|
|||
dataSourceProfileService,
|
||||
documentProfileService,
|
||||
enabledExperimentalProfileIds,
|
||||
services: this.getDiscoverServices(core, plugins, profilesManager, ebtContextManager),
|
||||
services: this.getDiscoverServices(core, plugins, profilesManager, ebtManager),
|
||||
});
|
||||
|
||||
return profilesManager;
|
||||
};
|
||||
|
||||
private createEmptyProfilesManager() {
|
||||
const { rootProfileService, dataSourceProfileService, documentProfileService } =
|
||||
this.createProfileServices();
|
||||
}
|
||||
|
||||
private createEmptyProfilesManager({ ebtManager }: { ebtManager: DiscoverEBTManager }) {
|
||||
return new ProfilesManager(
|
||||
rootProfileService,
|
||||
dataSourceProfileService,
|
||||
documentProfileService,
|
||||
new DiscoverEBTContextManager() // it's not enabled outside of Discover
|
||||
new RootProfileService(),
|
||||
new DataSourceProfileService(),
|
||||
new DocumentProfileService(),
|
||||
ebtManager
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -371,7 +377,7 @@ export class DiscoverPlugin
|
|||
core: CoreStart,
|
||||
plugins: DiscoverStartPlugins,
|
||||
profilesManager: ProfilesManager,
|
||||
ebtContextManager: DiscoverEBTContextManager
|
||||
ebtManager: DiscoverEBTManager
|
||||
) => {
|
||||
return buildServices({
|
||||
core,
|
||||
|
@ -383,11 +389,13 @@ export class DiscoverPlugin
|
|||
history: this.historyService.getHistory(),
|
||||
urlTracker: this.urlTracker!,
|
||||
profilesManager,
|
||||
ebtContextManager,
|
||||
ebtManager,
|
||||
});
|
||||
};
|
||||
|
||||
private registerEmbeddable(core: CoreSetup<DiscoverStartPlugins>, plugins: DiscoverSetupPlugins) {
|
||||
const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover
|
||||
|
||||
const getStartServices = async () => {
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
return {
|
||||
|
@ -396,16 +404,20 @@ export class DiscoverPlugin
|
|||
};
|
||||
};
|
||||
|
||||
const getDiscoverServicesInternal = async () => {
|
||||
const getDiscoverServicesForEmbeddable = async () => {
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
const ebtContextManager = new DiscoverEBTContextManager(); // it's not enabled outside of Discover
|
||||
const profilesManager = await this.createProfilesManager(coreStart, deps, ebtContextManager);
|
||||
return this.getDiscoverServices(coreStart, deps, profilesManager, ebtContextManager);
|
||||
|
||||
const profilesManager = await this.createProfilesManager({
|
||||
core: coreStart,
|
||||
plugins: deps,
|
||||
ebtManager,
|
||||
});
|
||||
return this.getDiscoverServices(coreStart, deps, profilesManager, ebtManager);
|
||||
};
|
||||
|
||||
plugins.embeddable.registerReactEmbeddableSavedObject<SavedSearchAttributes>({
|
||||
onAdd: async (container, savedObject) => {
|
||||
const services = await getDiscoverServicesInternal();
|
||||
const services = await getDiscoverServicesForEmbeddable();
|
||||
const initialState = await deserializeState({
|
||||
serializedState: {
|
||||
rawState: { savedObjectId: savedObject.id },
|
||||
|
@ -429,7 +441,7 @@ export class DiscoverPlugin
|
|||
plugins.embeddable.registerReactEmbeddableFactory(SEARCH_EMBEDDABLE_TYPE, async () => {
|
||||
const [startServices, discoverServices, { getSearchEmbeddableFactory }] = await Promise.all([
|
||||
getStartServices(),
|
||||
getDiscoverServicesInternal(),
|
||||
getDiscoverServicesForEmbeddable(),
|
||||
import('./embeddable/get_search_embeddable_factory'),
|
||||
]);
|
||||
|
||||
|
|
|
@ -1,95 +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 { DiscoverEBTContextManager } from './discover_ebt_context_manager';
|
||||
|
||||
const coreSetupMock = coreMock.createSetup();
|
||||
|
||||
describe('DiscoverEBTContextManager', () => {
|
||||
let discoverEBTContextManager: DiscoverEBTContextManager;
|
||||
|
||||
beforeEach(() => {
|
||||
discoverEBTContextManager = new DiscoverEBTContextManager();
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register the context provider', () => {
|
||||
discoverEBTContextManager.initialize({ core: coreSetupMock });
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateProfilesWith', () => {
|
||||
it('should update the profiles with the provided props', () => {
|
||||
const dscProfiles = ['profile1', 'profile2'];
|
||||
const dscProfiles2 = ['profile21', 'profile22'];
|
||||
discoverEBTContextManager.initialize({ core: coreSetupMock });
|
||||
discoverEBTContextManager.enable();
|
||||
|
||||
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 });
|
||||
discoverEBTContextManager.enable();
|
||||
|
||||
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 });
|
||||
|
||||
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 });
|
||||
discoverEBTContextManager.enable();
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
discoverEBTContextManager.disableAndReset();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.enable();
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,75 +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 { isEqual } from 'lodash';
|
||||
import type { CoreSetup } from '@kbn/core-lifecycle-browser';
|
||||
|
||||
export interface DiscoverEBTContextProps {
|
||||
discoverProfiles: string[]; // Discover Context Awareness Profiles
|
||||
}
|
||||
export type DiscoverEBTContext = BehaviorSubject<DiscoverEBTContextProps>;
|
||||
|
||||
export class DiscoverEBTContextManager {
|
||||
private isEnabled: boolean = false;
|
||||
private ebtContext$: DiscoverEBTContext | undefined;
|
||||
|
||||
constructor() {}
|
||||
|
||||
// https://docs.elastic.dev/telemetry/collection/event-based-telemetry
|
||||
public initialize({ core }: { core: CoreSetup }) {
|
||||
const context$ = new BehaviorSubject<DiscoverEBTContextProps>({
|
||||
discoverProfiles: [],
|
||||
});
|
||||
|
||||
core.analytics.registerContextProvider({
|
||||
name: 'discover_context',
|
||||
context$,
|
||||
schema: {
|
||||
discoverProfiles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'List of active Discover context awareness profiles',
|
||||
},
|
||||
},
|
||||
},
|
||||
// If we decide to extend EBT context with more properties, we can do it here
|
||||
},
|
||||
});
|
||||
|
||||
this.ebtContext$ = context$;
|
||||
}
|
||||
|
||||
public enable() {
|
||||
this.isEnabled = true;
|
||||
}
|
||||
|
||||
public updateProfilesContextWith(discoverProfiles: DiscoverEBTContextProps['discoverProfiles']) {
|
||||
if (
|
||||
this.isEnabled &&
|
||||
this.ebtContext$ &&
|
||||
!isEqual(this.ebtContext$.getValue().discoverProfiles, discoverProfiles)
|
||||
) {
|
||||
this.ebtContext$.next({
|
||||
discoverProfiles,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getProfilesContext() {
|
||||
return this.ebtContext$?.getValue()?.discoverProfiles;
|
||||
}
|
||||
|
||||
public disableAndReset() {
|
||||
this.updateProfilesContextWith([]);
|
||||
this.isEnabled = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* 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 { DiscoverEBTManager } from './discover_ebt_manager';
|
||||
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
describe('DiscoverEBTManager', () => {
|
||||
let discoverEBTContextManager: DiscoverEBTManager;
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
it('should register the context provider and custom events', () => {
|
||||
discoverEBTContextManager.initialize({
|
||||
core: coreSetupMock,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: true,
|
||||
});
|
||||
|
||||
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,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: false,
|
||||
});
|
||||
discoverEBTContextManager.enableContext();
|
||||
|
||||
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,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: false,
|
||||
});
|
||||
discoverEBTContextManager.enableContext();
|
||||
|
||||
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,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: false,
|
||||
});
|
||||
|
||||
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,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: false,
|
||||
});
|
||||
discoverEBTContextManager.enableContext();
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toBe(dscProfiles);
|
||||
discoverEBTContextManager.disableAndResetContext();
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.updateProfilesContextWith(dscProfiles);
|
||||
expect(discoverEBTContextManager.getProfilesContext()).toEqual([]);
|
||||
discoverEBTContextManager.enableContext();
|
||||
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,
|
||||
shouldInitializeCustomContext: false,
|
||||
shouldInitializeCustomEvents: true,
|
||||
});
|
||||
|
||||
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,
|
||||
shouldInitializeCustomContext: false,
|
||||
shouldInitializeCustomEvents: true,
|
||||
});
|
||||
|
||||
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,
|
||||
shouldInitializeCustomContext: false,
|
||||
shouldInitializeCustomEvents: true,
|
||||
});
|
||||
|
||||
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_',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
219
src/plugins/discover/public/services/discover_ebt_manager.ts
Normal file
219
src/plugins/discover/public/services/discover_ebt_manager.ts
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* 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 { isEqual } from 'lodash';
|
||||
import type { CoreSetup } from '@kbn/core-lifecycle-browser';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
||||
const FIELD_USAGE_EVENT_TYPE = 'discover_field_usage';
|
||||
const FIELD_USAGE_EVENT_NAME = 'eventName';
|
||||
const FIELD_USAGE_FIELD_NAME = 'fieldName';
|
||||
const FIELD_USAGE_FILTER_OPERATION = 'filterOperation';
|
||||
|
||||
type FilterOperation = '+' | '-' | '_exists_';
|
||||
|
||||
export enum FieldUsageEventName {
|
||||
dataTableSelection = 'dataTableSelection',
|
||||
dataTableRemoval = 'dataTableRemoval',
|
||||
filterAddition = 'filterAddition',
|
||||
}
|
||||
interface FieldUsageEventData {
|
||||
[FIELD_USAGE_EVENT_NAME]: FieldUsageEventName;
|
||||
[FIELD_USAGE_FIELD_NAME]?: string;
|
||||
[FIELD_USAGE_FILTER_OPERATION]?: FilterOperation;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
constructor() {}
|
||||
|
||||
// https://docs.elastic.dev/telemetry/collection/event-based-telemetry
|
||||
public initialize({
|
||||
core,
|
||||
shouldInitializeCustomContext,
|
||||
shouldInitializeCustomEvents,
|
||||
}: {
|
||||
core: CoreSetup;
|
||||
shouldInitializeCustomContext: boolean;
|
||||
shouldInitializeCustomEvents: boolean;
|
||||
}) {
|
||||
if (shouldInitializeCustomContext) {
|
||||
// Register Discover specific context to be used in EBT
|
||||
const context$ = new BehaviorSubject<DiscoverEBTContextProps>({
|
||||
discoverProfiles: [],
|
||||
});
|
||||
core.analytics.registerContextProvider({
|
||||
name: 'discover_context',
|
||||
context$,
|
||||
schema: {
|
||||
discoverProfiles: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'List of active Discover context awareness profiles',
|
||||
},
|
||||
},
|
||||
},
|
||||
// If we decide to extend EBT context with more properties, we can do it here
|
||||
},
|
||||
});
|
||||
this.customContext$ = context$;
|
||||
}
|
||||
|
||||
if (shouldInitializeCustomEvents) {
|
||||
// Register Discover events to be used with EBT
|
||||
core.analytics.registerEventType({
|
||||
eventType: FIELD_USAGE_EVENT_TYPE,
|
||||
schema: {
|
||||
[FIELD_USAGE_EVENT_NAME]: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description:
|
||||
'The name of the event that is tracked in the metrics i.e. dataTableSelection, dataTableRemoval',
|
||||
},
|
||||
},
|
||||
[FIELD_USAGE_FIELD_NAME]: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: "Field name if it's a part of ECS schema",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
[FIELD_USAGE_FILTER_OPERATION]: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: "Operation type when a filter is added i.e. '+', '-', '_exists_'",
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.reportEvent = core.analytics.reportEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public enableContext() {
|
||||
this.isCustomContextEnabled = true;
|
||||
}
|
||||
|
||||
public disableAndResetContext() {
|
||||
this.updateProfilesContextWith([]);
|
||||
this.isCustomContextEnabled = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private async trackFieldUsageEvent({
|
||||
eventName,
|
||||
fieldName,
|
||||
filterOperation,
|
||||
fieldsMetadata,
|
||||
}: {
|
||||
eventName: FieldUsageEventName;
|
||||
fieldName: string;
|
||||
filterOperation?: FilterOperation;
|
||||
fieldsMetadata: FieldsMetadataPublicStart | undefined;
|
||||
}) {
|
||||
if (!this.reportEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventData: FieldUsageEventData = {
|
||||
[FIELD_USAGE_EVENT_NAME]: eventName,
|
||||
};
|
||||
|
||||
if (fieldsMetadata) {
|
||||
const client = await fieldsMetadata.getClient();
|
||||
const { fields } = await client.find({
|
||||
attributes: ['short'],
|
||||
fieldNames: [fieldName],
|
||||
});
|
||||
|
||||
// excludes non ECS fields
|
||||
if (fields[fieldName]?.short) {
|
||||
eventData[FIELD_USAGE_FIELD_NAME] = fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
if (filterOperation) {
|
||||
eventData[FIELD_USAGE_FILTER_OPERATION] = filterOperation;
|
||||
}
|
||||
|
||||
this.reportEvent(FIELD_USAGE_EVENT_TYPE, eventData);
|
||||
}
|
||||
|
||||
public async trackDataTableSelection({
|
||||
fieldName,
|
||||
fieldsMetadata,
|
||||
}: {
|
||||
fieldName: string;
|
||||
fieldsMetadata: FieldsMetadataPublicStart | undefined;
|
||||
}) {
|
||||
await this.trackFieldUsageEvent({
|
||||
eventName: FieldUsageEventName.dataTableSelection,
|
||||
fieldName,
|
||||
fieldsMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
public async trackDataTableRemoval({
|
||||
fieldName,
|
||||
fieldsMetadata,
|
||||
}: {
|
||||
fieldName: string;
|
||||
fieldsMetadata: FieldsMetadataPublicStart | undefined;
|
||||
}) {
|
||||
await this.trackFieldUsageEvent({
|
||||
eventName: FieldUsageEventName.dataTableRemoval,
|
||||
fieldName,
|
||||
fieldsMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
public async trackFilterAddition({
|
||||
fieldName,
|
||||
fieldsMetadata,
|
||||
filterOperation,
|
||||
}: {
|
||||
fieldName: string;
|
||||
fieldsMetadata: FieldsMetadataPublicStart | undefined;
|
||||
filterOperation: FilterOperation;
|
||||
}) {
|
||||
await this.trackFieldUsageEvent({
|
||||
eventName: FieldUsageEventName.filterAddition,
|
||||
fieldName,
|
||||
fieldsMetadata,
|
||||
filterOperation,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -12,115 +12,16 @@ import expect from '@kbn/expect';
|
|||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const { common, discover, unifiedFieldList, dashboard, header, timePicker } = getPageObjects([
|
||||
const { common, discover, unifiedFieldList } = getPageObjects([
|
||||
'common',
|
||||
'discover',
|
||||
'unifiedFieldList',
|
||||
'dashboard',
|
||||
'header',
|
||||
'timePicker',
|
||||
]);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dataViews = getService('dataViews');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const monacoEditor = getService('monacoEditor');
|
||||
const ebtUIHelper = getService('kibana_ebt_ui');
|
||||
const retry = getService('retry');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
|
||||
describe('data source profile', () => {
|
||||
describe('telemetry', () => {
|
||||
before(async () => {
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
|
||||
});
|
||||
|
||||
it('should set EBT context for telemetry events with default profile', async () => {
|
||||
await common.navigateToApp('discover');
|
||||
await discover.selectTextBaseLang();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await monacoEditor.setCodeEditorValue('from my-example-* | sort @timestamp desc');
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
const events = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['performance_metric'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(events[events.length - 1].context.discoverProfiles).to.eql([
|
||||
'example-root-profile',
|
||||
'default-data-source-profile',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set EBT context for telemetry events when example profile and reset', async () => {
|
||||
await common.navigateToApp('discover');
|
||||
await discover.selectTextBaseLang();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await monacoEditor.setCodeEditorValue('from my-example-logs | sort @timestamp desc');
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
const events = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['performance_metric'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(events[events.length - 1].context.discoverProfiles).to.eql([
|
||||
'example-root-profile',
|
||||
'example-data-source-profile',
|
||||
]);
|
||||
|
||||
// should reset the profiles when navigating away from Discover
|
||||
await testSubjects.click('logo');
|
||||
await retry.waitFor('home page to open', async () => {
|
||||
return (await testSubjects.getVisibleText('euiBreadcrumb')) === 'Home';
|
||||
});
|
||||
await testSubjects.click('addSampleData');
|
||||
|
||||
await retry.try(async () => {
|
||||
const eventsAfter = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['click'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(eventsAfter[eventsAfter.length - 1].context.discoverProfiles).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set EBT context for embeddables', async () => {
|
||||
await dashboard.navigateToApp();
|
||||
await dashboard.gotoDashboardLandingPage();
|
||||
await dashboard.clickNewDashboard();
|
||||
await timePicker.setDefaultAbsoluteRange();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await dashboardAddPanel.addSavedSearch('A Saved Search');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await dashboard.waitForRenderComplete();
|
||||
const rows = await dataGrid.getDocTableRows();
|
||||
expect(rows.length).to.be.above(0);
|
||||
await testSubjects.click('dashboardEditorMenuButton');
|
||||
|
||||
const events = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['click'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(
|
||||
events.every((event) => !(event.context.discoverProfiles as string[])?.length)
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ES|QL mode', () => {
|
||||
describe('cell renderers', () => {
|
||||
it('should render custom @timestamp but not custom log.level', async () => {
|
||||
|
|
326
test/functional/apps/discover/context_awareness/_telemetry.ts
Normal file
326
test/functional/apps/discover/context_awareness/_telemetry.ts
Normal file
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const { common, discover, unifiedFieldList, dashboard, header, timePicker } = getPageObjects([
|
||||
'common',
|
||||
'discover',
|
||||
'unifiedFieldList',
|
||||
'dashboard',
|
||||
'header',
|
||||
'timePicker',
|
||||
]);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const dataViews = getService('dataViews');
|
||||
const monacoEditor = getService('monacoEditor');
|
||||
const ebtUIHelper = getService('kibana_ebt_ui');
|
||||
const retry = getService('retry');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
|
||||
describe('telemetry', () => {
|
||||
describe('context', () => {
|
||||
before(async () => {
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
|
||||
});
|
||||
|
||||
it('should set EBT context for telemetry events with default profile', async () => {
|
||||
await common.navigateToApp('discover');
|
||||
await discover.selectTextBaseLang();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await monacoEditor.setCodeEditorValue('from my-example-* | sort @timestamp desc');
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
const events = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['performance_metric'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(events[events.length - 1].context.discoverProfiles).to.eql([
|
||||
'example-root-profile',
|
||||
'default-data-source-profile',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should set EBT context for telemetry events when example profile and reset', async () => {
|
||||
await common.navigateToApp('discover');
|
||||
await discover.selectTextBaseLang();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await monacoEditor.setCodeEditorValue('from my-example-logs | sort @timestamp desc');
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await testSubjects.click('querySubmitButton');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
const events = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['performance_metric'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(events[events.length - 1].context.discoverProfiles).to.eql([
|
||||
'example-root-profile',
|
||||
'example-data-source-profile',
|
||||
]);
|
||||
|
||||
// should reset the profiles when navigating away from Discover
|
||||
await testSubjects.click('logo');
|
||||
await retry.waitFor('home page to open', async () => {
|
||||
return (await testSubjects.getVisibleText('euiBreadcrumb')) === 'Home';
|
||||
});
|
||||
await testSubjects.click('addSampleData');
|
||||
|
||||
await retry.try(async () => {
|
||||
const eventsAfter = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['click'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(eventsAfter[eventsAfter.length - 1].context.discoverProfiles).to.eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set EBT context for embeddables', async () => {
|
||||
await dashboard.navigateToApp();
|
||||
await dashboard.gotoDashboardLandingPage();
|
||||
await dashboard.clickNewDashboard();
|
||||
await timePicker.setDefaultAbsoluteRange();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await dashboardAddPanel.addSavedSearch('A Saved Search');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await dashboard.waitForRenderComplete();
|
||||
const rows = await dataGrid.getDocTableRows();
|
||||
expect(rows.length).to.be.above(0);
|
||||
await testSubjects.click('dashboardEditorMenuButton');
|
||||
|
||||
const events = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['click'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(
|
||||
events.length > 0 &&
|
||||
events.every((event) => !(event.context.discoverProfiles as string[])?.length)
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
beforeEach(async () => {
|
||||
await common.navigateToApp('discover');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
});
|
||||
|
||||
it('should track field usage when a field is added to the table', async () => {
|
||||
await dataViews.switchToAndValidate('my-example-*');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await unifiedFieldList.clickFieldListItemAdd('service.name');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
const [event] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event.properties).to.eql({
|
||||
eventName: 'dataTableSelection',
|
||||
fieldName: 'service.name',
|
||||
});
|
||||
|
||||
await unifiedFieldList.clickFieldListItemAdd('_score');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
const [_, event2] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event2.properties).to.eql({
|
||||
eventName: 'dataTableSelection',
|
||||
});
|
||||
});
|
||||
|
||||
it('should track field usage when a field is removed from the table', async () => {
|
||||
await dataViews.switchToAndValidate('my-example-logs');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await unifiedFieldList.clickFieldListItemRemove('log.level');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
const [event] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event.properties).to.eql({
|
||||
eventName: 'dataTableRemoval',
|
||||
fieldName: 'log.level',
|
||||
});
|
||||
});
|
||||
|
||||
it('should track field usage when a filter is added', async () => {
|
||||
await dataViews.switchToAndValidate('my-example-logs');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 0);
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
const [event] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event.properties).to.eql({
|
||||
eventName: 'filterAddition',
|
||||
fieldName: '@timestamp',
|
||||
filterOperation: '+',
|
||||
});
|
||||
|
||||
await unifiedFieldList.clickFieldListExistsFilter('log.level');
|
||||
|
||||
const [_, event2] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event2.properties).to.eql({
|
||||
eventName: 'filterAddition',
|
||||
fieldName: 'log.level',
|
||||
filterOperation: '_exists_',
|
||||
});
|
||||
});
|
||||
|
||||
it('should track field usage for doc viewer too', async () => {
|
||||
await dataViews.switchToAndValidate('my-example-logs');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
|
||||
await dataGrid.clickRowToggle();
|
||||
await discover.isShowingDocViewer();
|
||||
|
||||
// event 1
|
||||
await dataGrid.clickFieldActionInFlyout('service.name', 'toggleColumnButton');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
// event 2
|
||||
await dataGrid.clickFieldActionInFlyout('log.level', 'toggleColumnButton');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
// event 3
|
||||
await dataGrid.clickFieldActionInFlyout('log.level', 'addFilterOutValueButton');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
const [event1, event2, event3] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event1.properties).to.eql({
|
||||
eventName: 'dataTableSelection',
|
||||
fieldName: 'service.name',
|
||||
});
|
||||
|
||||
expect(event2.properties).to.eql({
|
||||
eventName: 'dataTableRemoval',
|
||||
fieldName: 'log.level',
|
||||
});
|
||||
|
||||
expect(event3.properties).to.eql({
|
||||
eventName: 'filterAddition',
|
||||
fieldName: 'log.level',
|
||||
filterOperation: '-',
|
||||
});
|
||||
});
|
||||
|
||||
it('should track field usage on surrounding documents page', async () => {
|
||||
await dataViews.switchToAndValidate('my-example-logs');
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
await unifiedFieldList.waitUntilSidebarHasLoaded();
|
||||
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await discover.isShowingDocViewer();
|
||||
|
||||
const [, surroundingActionEl] = await dataGrid.getRowActions();
|
||||
await surroundingActionEl.click();
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await ebtUIHelper.setOptIn(true);
|
||||
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
await discover.isShowingDocViewer();
|
||||
|
||||
// event 1
|
||||
await dataGrid.clickFieldActionInFlyout('service.name', 'toggleColumnButton');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
// event 2
|
||||
await dataGrid.clickFieldActionInFlyout('log.level', 'toggleColumnButton');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
// event 3
|
||||
await dataGrid.clickFieldActionInFlyout('log.level', 'addFilterOutValueButton');
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await discover.waitUntilSearchingHasFinished();
|
||||
|
||||
const [event1, event2, event3] = await ebtUIHelper.getEvents(Number.MAX_SAFE_INTEGER, {
|
||||
eventTypes: ['discover_field_usage'],
|
||||
withTimeoutMs: 500,
|
||||
});
|
||||
|
||||
expect(event1.properties).to.eql({
|
||||
eventName: 'dataTableSelection',
|
||||
fieldName: 'service.name',
|
||||
});
|
||||
|
||||
expect(event2.properties).to.eql({
|
||||
eventName: 'dataTableRemoval',
|
||||
fieldName: 'log.level',
|
||||
});
|
||||
|
||||
expect(event3.properties).to.eql({
|
||||
eventName: 'filterAddition',
|
||||
fieldName: 'log.level',
|
||||
filterOperation: '-',
|
||||
});
|
||||
|
||||
expect(event3.context.discoverProfiles).to.eql([
|
||||
'example-root-profile',
|
||||
'example-data-source-profile',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -38,6 +38,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
|
|||
|
||||
loadTestFile(require.resolve('./_root_profile'));
|
||||
loadTestFile(require.resolve('./_data_source_profile'));
|
||||
loadTestFile(require.resolve('./_telemetry'));
|
||||
loadTestFile(require.resolve('./extensions/_get_row_indicator_provider'));
|
||||
loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls'));
|
||||
loadTestFile(require.resolve('./extensions/_get_doc_viewer'));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue