mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] [Behavioral analytics] Add Explorer page (#155077)
Implement Explorer page that consists of: - ✔️ Tabs [Search terms, Top clicked results, No results, Referrers] - ✔️ Search bar for searching the data - ✔️ Data representation for top results as a table with paginations, sorting, page size - ✔️ Callout "Need a deeper analysis" <img width="1168" alt="image" src="https://user-images.githubusercontent.com/17390745/232572642-ab6374bc-a798-4d6a-93db-36acf141f931.png"> --------- Co-authored-by: Klim Markelov <klim.markelov@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c3c55e7aa8
commit
0225747610
33 changed files with 1667 additions and 662 deletions
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogicMounter } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { AnalyticsCollection } from '../../../../../common/types/analytics';
|
||||
|
||||
import { findOrCreateDataView } from '../../utils/find_or_create_data_view';
|
||||
|
||||
import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic';
|
||||
import { FetchAnalyticsCollectionLogic } from './fetch_analytics_collection_logic';
|
||||
|
||||
jest.mock('../../utils/find_or_create_data_view', () => {
|
||||
return {
|
||||
findOrCreateDataView: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('AnalyticsCollectionDataViewLogic', () => {
|
||||
const { mount } = new LogicMounter(AnalyticsCollectionDataViewLogic);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mount();
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
dataView: null,
|
||||
};
|
||||
|
||||
it('initializes with default values', () => {
|
||||
expect(AnalyticsCollectionDataViewLogic.values).toEqual(defaultProps);
|
||||
});
|
||||
|
||||
describe('reducers', () => {
|
||||
it('should handle set dataView', () => {
|
||||
const dataView = { id: 'test' } as DataView;
|
||||
AnalyticsCollectionDataViewLogic.actions.setDataView(dataView);
|
||||
expect(AnalyticsCollectionDataViewLogic.values.dataView).toBe(dataView);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('should find and set dataView when analytics collection fetched', async () => {
|
||||
const dataView = { id: 'test' } as DataView;
|
||||
(findOrCreateDataView as jest.Mock).mockResolvedValue(dataView);
|
||||
|
||||
await FetchAnalyticsCollectionLogic.actions.apiSuccess({
|
||||
events_datastream: 'events1',
|
||||
name: 'collection1',
|
||||
} as AnalyticsCollection);
|
||||
|
||||
expect(AnalyticsCollectionDataViewLogic.values.dataView).toEqual(dataView);
|
||||
});
|
||||
|
||||
it('should create, save and set dataView when analytics collection fetched but dataView is not found', async () => {
|
||||
const dataView = { id: 'test' } as DataView;
|
||||
(findOrCreateDataView as jest.Mock).mockResolvedValue(dataView);
|
||||
|
||||
await FetchAnalyticsCollectionLogic.actions.apiSuccess({
|
||||
events_datastream: 'events1',
|
||||
name: 'collection1',
|
||||
} as AnalyticsCollection);
|
||||
|
||||
expect(AnalyticsCollectionDataViewLogic.values.dataView).toEqual(dataView);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { findOrCreateDataView } from '../../utils/find_or_create_data_view';
|
||||
|
||||
import {
|
||||
FetchAnalyticsCollectionActions,
|
||||
FetchAnalyticsCollectionLogic,
|
||||
} from './fetch_analytics_collection_logic';
|
||||
|
||||
interface AnalyticsCollectionDataViewLogicValues {
|
||||
dataView: DataView | null;
|
||||
}
|
||||
|
||||
interface AnalyticsCollectionDataViewLogicActions {
|
||||
fetchedAnalyticsCollection: FetchAnalyticsCollectionActions['apiSuccess'];
|
||||
setDataView(dataView: DataView): { dataView: DataView };
|
||||
}
|
||||
|
||||
export const AnalyticsCollectionDataViewLogic = kea<
|
||||
MakeLogicType<AnalyticsCollectionDataViewLogicValues, AnalyticsCollectionDataViewLogicActions>
|
||||
>({
|
||||
actions: {
|
||||
setDataView: (dataView) => ({ dataView }),
|
||||
},
|
||||
connect: {
|
||||
actions: [FetchAnalyticsCollectionLogic, ['apiSuccess as fetchedAnalyticsCollection']],
|
||||
},
|
||||
listeners: ({ actions }) => ({
|
||||
fetchedAnalyticsCollection: async (collection) => {
|
||||
actions.setDataView(await findOrCreateDataView(collection));
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'analytics', 'collections', 'dataView'],
|
||||
reducers: () => ({
|
||||
dataView: [null, { setDataView: (_, { dataView }) => dataView }],
|
||||
}),
|
||||
});
|
|
@ -1,149 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogicMounter } from '../../../../__mocks__/kea_logic';
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { AnalyticsCollection } from '../../../../../../common/types/analytics';
|
||||
|
||||
import { KibanaLogic } from '../../../../shared/kibana/kibana_logic';
|
||||
|
||||
import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
||||
import {
|
||||
AnalyticsCollectionExploreTableLogic,
|
||||
Sorting,
|
||||
} from './analytics_collection_explore_table_logic';
|
||||
import { ExploreTableColumns, ExploreTables } from './analytics_collection_explore_table_types';
|
||||
|
||||
jest.mock('../../../../shared/kibana/kibana_logic', () => ({
|
||||
KibanaLogic: {
|
||||
values: {
|
||||
data: {
|
||||
dataViews: {
|
||||
find: jest.fn(() => Promise.resolve([{ id: 'some-data-view-id' }])),
|
||||
},
|
||||
search: {
|
||||
search: jest.fn().mockReturnValue({ subscribe: jest.fn() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AnalyticsCollectionExplorerTablesLogic', () => {
|
||||
const { mount } = new LogicMounter(AnalyticsCollectionExploreTableLogic);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mount();
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
dataView: null,
|
||||
isLoading: false,
|
||||
items: [],
|
||||
selectedTable: null,
|
||||
sorting: null,
|
||||
};
|
||||
|
||||
it('initializes with default values', () => {
|
||||
expect(AnalyticsCollectionExploreTableLogic.values).toEqual(defaultProps);
|
||||
});
|
||||
|
||||
describe('reducers', () => {
|
||||
it('should handle set dataView', () => {
|
||||
const dataView = { id: 'test' } as DataView;
|
||||
AnalyticsCollectionExploreTableLogic.actions.setDataView(dataView);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.dataView).toBe(dataView);
|
||||
});
|
||||
|
||||
it('should handle set items', () => {
|
||||
const items = [
|
||||
{ count: 1, query: 'test' },
|
||||
{ count: 2, query: 'test2' },
|
||||
];
|
||||
AnalyticsCollectionExploreTableLogic.actions.setItems(items);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.items).toEqual(items);
|
||||
});
|
||||
|
||||
it('should handle set selectedTable', () => {
|
||||
const id = ExploreTables.WorsePerformers;
|
||||
const sorting = { direction: 'desc', field: ExploreTableColumns.count } as Sorting;
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(id, sorting);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.selectedTable).toEqual(id);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting);
|
||||
});
|
||||
|
||||
it('should handle set sorting', () => {
|
||||
const sorting = { direction: 'asc', field: ExploreTableColumns.sessions } as Sorting;
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSorting(sorting);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting);
|
||||
});
|
||||
|
||||
it('should handle isLoading', () => {
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(false);
|
||||
|
||||
AnalyticsCollectionExploreTableLogic.actions.setItems([]);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(false);
|
||||
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
|
||||
AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' });
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
|
||||
AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('12345');
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('should fetch items when selectedTable changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers);
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch items when timeRange changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();
|
||||
|
||||
AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' });
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch items when searchSessionId changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();
|
||||
|
||||
AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('1234');
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: '1234',
|
||||
});
|
||||
});
|
||||
|
||||
it('should find and set dataView when findDataView is called', async () => {
|
||||
const dataView = { id: 'test' } as DataView;
|
||||
jest.spyOn(KibanaLogic.values.data.dataViews, 'find').mockResolvedValue([dataView]);
|
||||
await AnalyticsCollectionExploreTableLogic.actions.findDataView({
|
||||
events_datastream: 'events1',
|
||||
name: 'collection1',
|
||||
} as AnalyticsCollection);
|
||||
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.dataView).toEqual(dataView);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,333 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import {
|
||||
IKibanaSearchRequest,
|
||||
IKibanaSearchResponse,
|
||||
isCompleteResponse,
|
||||
TimeRange,
|
||||
} from '@kbn/data-plugin/common';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { AnalyticsCollection } from '../../../../../../common/types/analytics';
|
||||
import { KibanaLogic } from '../../../../shared/kibana/kibana_logic';
|
||||
import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
||||
import {
|
||||
ExploreTableColumns,
|
||||
ExploreTableItem,
|
||||
ExploreTables,
|
||||
SearchTermsTable,
|
||||
TopClickedTable,
|
||||
TopReferrersTable,
|
||||
WorsePerformersTable,
|
||||
} from './analytics_collection_explore_table_types';
|
||||
|
||||
const BASE_PAGE_SIZE = 10;
|
||||
|
||||
export interface Sorting<T extends ExploreTableItem = ExploreTableItem> {
|
||||
direction: 'asc' | 'desc';
|
||||
field: keyof T;
|
||||
}
|
||||
|
||||
interface TableParams<T extends ExploreTableItem = ExploreTableItem> {
|
||||
parseResponseToItems(response: IKibanaSearchResponse): T[];
|
||||
requestParams(timeRange: TimeRange, sorting: Sorting<T> | null): IKibanaSearchRequest;
|
||||
}
|
||||
|
||||
const tablesParams: {
|
||||
[ExploreTables.SearchTerms]: TableParams<SearchTermsTable>;
|
||||
[ExploreTables.TopClicked]: TableParams<TopClickedTable>;
|
||||
[ExploreTables.TopReferrers]: TableParams<TopReferrersTable>;
|
||||
[ExploreTables.WorsePerformers]: TableParams<WorsePerformersTable>;
|
||||
} = {
|
||||
[ExploreTables.SearchTerms]: {
|
||||
parseResponseToItems: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: { searches: { buckets: Array<{ doc_count: number; key: string }> } };
|
||||
}>
|
||||
) =>
|
||||
response.rawResponse.aggregations.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.count]: bucket.doc_count,
|
||||
[ExploreTableColumns.searchTerms]: bucket.key,
|
||||
})),
|
||||
requestParams: (timeRange, sorting) => ({
|
||||
params: {
|
||||
aggs: {
|
||||
searches: {
|
||||
terms: {
|
||||
field: 'search.query',
|
||||
order: sorting
|
||||
? {
|
||||
[sorting.field === ExploreTableColumns.count ? '_count' : '_key']:
|
||||
sorting.direction,
|
||||
}
|
||||
: undefined,
|
||||
size: BASE_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: {
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: timeRange.from,
|
||||
lt: timeRange.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
track_total_hits: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
[ExploreTables.WorsePerformers]: {
|
||||
parseResponseToItems: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
formula: { searches: { buckets: Array<{ doc_count: number; key: string }> } };
|
||||
};
|
||||
}>
|
||||
) =>
|
||||
response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.count]: bucket.doc_count,
|
||||
[ExploreTableColumns.query]: bucket.key,
|
||||
})),
|
||||
requestParams: (timeRange, sorting) => ({
|
||||
params: {
|
||||
aggs: {
|
||||
formula: {
|
||||
aggs: {
|
||||
searches: {
|
||||
terms: {
|
||||
field: 'search.query',
|
||||
order: sorting
|
||||
? {
|
||||
[sorting?.field === ExploreTableColumns.count ? '_count' : '_key']:
|
||||
sorting?.direction,
|
||||
}
|
||||
: undefined,
|
||||
size: BASE_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: { term: { 'search.results.total_results': '0' } },
|
||||
},
|
||||
},
|
||||
query: {
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: timeRange.from,
|
||||
lt: timeRange.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
track_total_hits: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
[ExploreTables.TopClicked]: {
|
||||
parseResponseToItems: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
formula: { searches: { buckets: Array<{ doc_count: number; key: string }> } };
|
||||
};
|
||||
}>
|
||||
) =>
|
||||
response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.count]: bucket.doc_count,
|
||||
[ExploreTableColumns.page]: bucket.key,
|
||||
})),
|
||||
requestParams: (timeRange, sorting) => ({
|
||||
params: {
|
||||
aggs: {
|
||||
formula: {
|
||||
aggs: {
|
||||
searches: {
|
||||
terms: {
|
||||
field: 'search.results.items.page.url',
|
||||
order: sorting
|
||||
? {
|
||||
[sorting.field === ExploreTableColumns.count ? '_count' : '_key']:
|
||||
sorting.direction,
|
||||
}
|
||||
: undefined,
|
||||
size: BASE_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: { term: { 'event.action': 'search_click' } },
|
||||
},
|
||||
},
|
||||
query: {
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: timeRange.from,
|
||||
lt: timeRange.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
track_total_hits: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
[ExploreTables.TopReferrers]: {
|
||||
parseResponseToItems: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
formula: { searches: { buckets: Array<{ doc_count: number; key: string }> } };
|
||||
};
|
||||
}>
|
||||
) =>
|
||||
response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.sessions]: bucket.doc_count,
|
||||
[ExploreTableColumns.page]: bucket.key,
|
||||
})),
|
||||
requestParams: (timeRange, sorting) => ({
|
||||
params: {
|
||||
aggs: {
|
||||
formula: {
|
||||
aggs: {
|
||||
searches: {
|
||||
terms: {
|
||||
field: 'page.referrer',
|
||||
order: sorting
|
||||
? {
|
||||
[sorting?.field === ExploreTableColumns.sessions ? '_count' : '_key']:
|
||||
sorting?.direction,
|
||||
}
|
||||
: undefined,
|
||||
size: BASE_PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: { term: { 'event.action': 'page_view' } },
|
||||
},
|
||||
},
|
||||
query: {
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: timeRange.from,
|
||||
lt: timeRange.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
track_total_hits: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export interface AnalyticsCollectionExploreTableLogicValues {
|
||||
dataView: DataView | null;
|
||||
isLoading: boolean;
|
||||
items: ExploreTableItem[];
|
||||
selectedTable: ExploreTables | null;
|
||||
sorting: Sorting | null;
|
||||
}
|
||||
|
||||
export interface AnalyticsCollectionExploreTableLogicActions {
|
||||
findDataView(collection: AnalyticsCollection): { collection: AnalyticsCollection };
|
||||
setDataView(dataView: DataView): { dataView: DataView };
|
||||
setItems(items: ExploreTableItem[]): { items: ExploreTableItem[] };
|
||||
setSelectedTable(
|
||||
id: ExploreTables | null,
|
||||
sorting?: Sorting
|
||||
): { id: ExploreTables | null; sorting?: Sorting };
|
||||
setSorting(sorting?: Sorting): { sorting?: Sorting };
|
||||
}
|
||||
|
||||
export const AnalyticsCollectionExploreTableLogic = kea<
|
||||
MakeLogicType<
|
||||
AnalyticsCollectionExploreTableLogicValues,
|
||||
AnalyticsCollectionExploreTableLogicActions
|
||||
>
|
||||
>({
|
||||
actions: {
|
||||
findDataView: (collection) => ({ collection }),
|
||||
setDataView: (dataView) => ({ dataView }),
|
||||
setItems: (items) => ({ items }),
|
||||
setSelectedTable: (id, sorting) => ({ id, sorting }),
|
||||
setSorting: (sorting) => ({ sorting }),
|
||||
},
|
||||
listeners: ({ actions, values }) => {
|
||||
const fetchItems = () => {
|
||||
if (values.selectedTable === null || !(values.selectedTable in tablesParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { requestParams, parseResponseToItems } = tablesParams[
|
||||
values.selectedTable
|
||||
] as TableParams;
|
||||
const timeRange = AnalyticsCollectionToolbarLogic.values.timeRange;
|
||||
|
||||
const search$ = KibanaLogic.values.data.search
|
||||
.search(requestParams(timeRange, values.sorting), {
|
||||
indexPattern: values.dataView || undefined,
|
||||
sessionId: AnalyticsCollectionToolbarLogic.values.searchSessionId,
|
||||
})
|
||||
.subscribe({
|
||||
error: (e) => {
|
||||
KibanaLogic.values.data.search.showError(e);
|
||||
},
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
actions.setItems(parseResponseToItems(response));
|
||||
search$.unsubscribe();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
findDataView: async ({ collection }) => {
|
||||
const dataView = (
|
||||
await KibanaLogic.values.data.dataViews.find(collection.events_datastream, 1)
|
||||
)?.[0];
|
||||
|
||||
if (dataView) {
|
||||
actions.setDataView(dataView);
|
||||
}
|
||||
},
|
||||
setSelectedTable: () => {
|
||||
fetchItems();
|
||||
},
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => {
|
||||
fetchItems();
|
||||
},
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => {
|
||||
fetchItems();
|
||||
},
|
||||
};
|
||||
},
|
||||
path: ['enterprise_search', 'analytics', 'collections', 'explore', 'table'],
|
||||
reducers: () => ({
|
||||
dataView: [null, { setDataView: (_, { dataView }) => dataView }],
|
||||
isLoading: [
|
||||
false,
|
||||
{
|
||||
setItems: () => false,
|
||||
setSelectedTable: () => true,
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => true,
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => true,
|
||||
},
|
||||
],
|
||||
items: [[], { setItems: (_, { items }) => items }],
|
||||
selectedTable: [null, { setSelectedTable: (_, { id }) => id }],
|
||||
sorting: [
|
||||
null,
|
||||
{
|
||||
setSelectedTable: (_, { sorting = null }) => sorting,
|
||||
setSorting: (_, { sorting = null }) => sorting,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IKibanaSearchRequest, TimeRange } from '@kbn/data-plugin/common';
|
||||
|
||||
const getSearchQueryRequestParams = (field: string, search: string): { regexp: {} } => {
|
||||
const createRegexQuery = (queryString: string) => {
|
||||
const query = queryString.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||
|
||||
return `.*${query}.*`;
|
||||
};
|
||||
|
||||
return {
|
||||
regexp: {
|
||||
[field]: {
|
||||
value: createRegexQuery(search),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
export const getTotalCountRequestParams = (field: string) => ({
|
||||
totalCount: {
|
||||
cardinality: {
|
||||
field,
|
||||
},
|
||||
},
|
||||
});
|
||||
export const getPaginationRequestSizeParams = (pageIndex: number, pageSize: number) => ({
|
||||
size: (pageIndex + 1) * pageSize,
|
||||
});
|
||||
export const getPaginationRequestParams = (pageIndex: number, pageSize: number) => ({
|
||||
aggs: {
|
||||
sort: {
|
||||
bucket_sort: {
|
||||
from: pageIndex * pageSize,
|
||||
size: pageSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const getBaseSearchTemplate = (
|
||||
aggregationFieldName: string,
|
||||
{ search, timeRange }: { search: string; timeRange: TimeRange },
|
||||
aggs: IKibanaSearchRequest['params']['aggs']
|
||||
): IKibanaSearchRequest => ({
|
||||
params: {
|
||||
aggs,
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: timeRange.from,
|
||||
lt: timeRange.to,
|
||||
},
|
||||
},
|
||||
},
|
||||
...(search ? [getSearchQueryRequestParams(aggregationFieldName, search)] : []),
|
||||
],
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
track_total_hits: false,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogicMounter } from '../../../__mocks__/kea_logic';
|
||||
|
||||
import { KibanaLogic } from '../../../shared/kibana/kibana_logic';
|
||||
|
||||
import {
|
||||
AnalyticsCollectionExploreTableLogic,
|
||||
Sorting,
|
||||
} from './analytics_collection_explore_table_logic';
|
||||
import { ExploreTableColumns, ExploreTables } from './analytics_collection_explore_table_types';
|
||||
import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
||||
jest.mock('../../../shared/kibana/kibana_logic', () => ({
|
||||
KibanaLogic: {
|
||||
values: {
|
||||
data: {
|
||||
search: {
|
||||
search: jest.fn().mockReturnValue({ subscribe: jest.fn() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AnalyticsCollectionExplorerTablesLogic', () => {
|
||||
const { mount } = new LogicMounter(AnalyticsCollectionExploreTableLogic);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mount();
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
isLoading: false,
|
||||
items: [],
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
search: '',
|
||||
selectedTable: null,
|
||||
sorting: null,
|
||||
totalItemsCount: 0,
|
||||
};
|
||||
|
||||
it('initializes with default values', () => {
|
||||
expect(AnalyticsCollectionExploreTableLogic.values).toEqual(defaultProps);
|
||||
});
|
||||
|
||||
describe('reducers', () => {
|
||||
it('should handle set items', () => {
|
||||
const items = [
|
||||
{ count: 1, query: 'test' },
|
||||
{ count: 2, query: 'test2' },
|
||||
];
|
||||
AnalyticsCollectionExploreTableLogic.actions.setItems(items);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.items).toEqual(items);
|
||||
});
|
||||
|
||||
it('should handle set selectedTable', () => {
|
||||
const id = ExploreTables.WorsePerformers;
|
||||
const sorting = { direction: 'desc', field: ExploreTableColumns.count } as Sorting;
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(id, sorting);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.selectedTable).toEqual(id);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting);
|
||||
});
|
||||
|
||||
it('should handle set sorting', () => {
|
||||
const sorting = { direction: 'asc', field: ExploreTableColumns.sessions } as Sorting;
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
sort: sorting,
|
||||
});
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.sorting).toEqual(sorting);
|
||||
});
|
||||
|
||||
describe('isLoading', () => {
|
||||
it('should handle onTableChange', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
sort: {
|
||||
direction: 'asc',
|
||||
field: ExploreTableColumns.sessions,
|
||||
} as Sorting,
|
||||
});
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle setSearch', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSearch('test');
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle setItems', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setItems([]);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(false);
|
||||
});
|
||||
|
||||
it('should handle setSelectedTable', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle setTimeRange', () => {
|
||||
AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' });
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle setSearchSessionId', () => {
|
||||
AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('12345');
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.isLoading).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageIndex', () => {
|
||||
it('should handle setPageIndex', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(2);
|
||||
});
|
||||
|
||||
it('should handle setSelectedTable', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0);
|
||||
});
|
||||
|
||||
it('should handle reset', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
AnalyticsCollectionExploreTableLogic.actions.reset();
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0);
|
||||
});
|
||||
|
||||
it('should handle setSearch', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSearch('');
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageIndex).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageSize', () => {
|
||||
it('should handle setPageSize', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10);
|
||||
});
|
||||
|
||||
it('should handle setSelectedTable', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10);
|
||||
});
|
||||
|
||||
it('should handle reset', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
|
||||
page: { index: 2, size: 10 },
|
||||
});
|
||||
AnalyticsCollectionExploreTableLogic.actions.reset();
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.pageSize).toEqual(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should handle setSearch', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSearch('test');
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual('test');
|
||||
});
|
||||
|
||||
it('should handle setSelectedTable', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSearch('test');
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual('');
|
||||
});
|
||||
|
||||
it('should handle reset', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSearch('test');
|
||||
AnalyticsCollectionExploreTableLogic.actions.reset();
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.search).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle totalItemsCount', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setTotalItemsCount(100);
|
||||
expect(AnalyticsCollectionExploreTableLogic.values.totalItemsCount).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('should fetch items when selectedTable changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.TopReferrers);
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch items when timeRange changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();
|
||||
|
||||
AnalyticsCollectionToolbarLogic.actions.setTimeRange({ from: 'now-7d', to: 'now' });
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch items when searchSessionId changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();
|
||||
|
||||
AnalyticsCollectionToolbarLogic.actions.setSearchSessionId('1234');
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: '1234',
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch items when onTableChange called', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();
|
||||
|
||||
AnalyticsCollectionExploreTableLogic.actions.onTableChange({});
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch items when search changes', () => {
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
|
||||
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();
|
||||
|
||||
AnalyticsCollectionExploreTableLogic.actions.setSearch('test');
|
||||
expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
|
||||
indexPattern: undefined,
|
||||
sessionId: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import {
|
||||
IKibanaSearchRequest,
|
||||
IKibanaSearchResponse,
|
||||
isCompleteResponse,
|
||||
TimeRange,
|
||||
} from '@kbn/data-plugin/common';
|
||||
|
||||
import { KibanaLogic } from '../../../shared/kibana/kibana_logic';
|
||||
|
||||
import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic';
|
||||
|
||||
import {
|
||||
getBaseSearchTemplate,
|
||||
getPaginationRequestParams,
|
||||
getPaginationRequestSizeParams,
|
||||
getTotalCountRequestParams,
|
||||
} from './analytics_collection_explore_table_formulas';
|
||||
import {
|
||||
ExploreTableColumns,
|
||||
ExploreTableItem,
|
||||
ExploreTables,
|
||||
SearchTermsTable,
|
||||
TopClickedTable,
|
||||
TopReferrersTable,
|
||||
WorsePerformersTable,
|
||||
} from './analytics_collection_explore_table_types';
|
||||
import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
||||
const BASE_PAGE_SIZE = 10;
|
||||
|
||||
export interface Sorting<T extends ExploreTableItem = ExploreTableItem> {
|
||||
direction: 'asc' | 'desc';
|
||||
field: keyof T;
|
||||
}
|
||||
|
||||
interface TableParams<T extends ExploreTableItem = ExploreTableItem> {
|
||||
parseResponse(response: IKibanaSearchResponse): { items: T[]; totalCount: number };
|
||||
requestParams(props: {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
search: string;
|
||||
sorting: Sorting<T> | null;
|
||||
timeRange: TimeRange;
|
||||
}): IKibanaSearchRequest;
|
||||
}
|
||||
|
||||
const tablesParams: {
|
||||
[ExploreTables.SearchTerms]: TableParams<SearchTermsTable>;
|
||||
[ExploreTables.TopClicked]: TableParams<TopClickedTable>;
|
||||
[ExploreTables.TopReferrers]: TableParams<TopReferrersTable>;
|
||||
[ExploreTables.WorsePerformers]: TableParams<WorsePerformersTable>;
|
||||
} = {
|
||||
[ExploreTables.SearchTerms]: {
|
||||
parseResponse: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
searches: { buckets: Array<{ doc_count: number; key: string }> };
|
||||
totalCount: { value: number };
|
||||
};
|
||||
}>
|
||||
) => ({
|
||||
items: response.rawResponse.aggregations.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.count]: bucket.doc_count,
|
||||
[ExploreTableColumns.searchTerms]: bucket.key,
|
||||
})),
|
||||
totalCount: response.rawResponse.aggregations.totalCount.value,
|
||||
}),
|
||||
requestParams: (
|
||||
{ timeRange, sorting, pageIndex, pageSize, search },
|
||||
aggregationFieldName = 'search.query'
|
||||
) =>
|
||||
getBaseSearchTemplate(
|
||||
aggregationFieldName,
|
||||
{ search, timeRange },
|
||||
{
|
||||
searches: {
|
||||
terms: {
|
||||
...getPaginationRequestSizeParams(pageIndex, pageSize),
|
||||
field: aggregationFieldName,
|
||||
order: sorting
|
||||
? {
|
||||
[sorting.field === ExploreTableColumns.count ? '_count' : '_key']:
|
||||
sorting.direction,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...getPaginationRequestParams(pageIndex, pageSize),
|
||||
},
|
||||
...getTotalCountRequestParams(aggregationFieldName),
|
||||
}
|
||||
),
|
||||
},
|
||||
[ExploreTables.WorsePerformers]: {
|
||||
parseResponse: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
formula: {
|
||||
searches: { buckets: Array<{ doc_count: number; key: string }> };
|
||||
totalCount: { value: number };
|
||||
};
|
||||
};
|
||||
}>
|
||||
) => ({
|
||||
items: response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.count]: bucket.doc_count,
|
||||
[ExploreTableColumns.query]: bucket.key,
|
||||
})),
|
||||
totalCount: response.rawResponse.aggregations.formula.totalCount.value,
|
||||
}),
|
||||
requestParams: (
|
||||
{ timeRange, sorting, pageIndex, pageSize, search },
|
||||
aggregationFieldName = 'search.query'
|
||||
) =>
|
||||
getBaseSearchTemplate(
|
||||
aggregationFieldName,
|
||||
{ search, timeRange },
|
||||
{
|
||||
formula: {
|
||||
aggs: {
|
||||
...getTotalCountRequestParams(aggregationFieldName),
|
||||
searches: {
|
||||
terms: {
|
||||
...getPaginationRequestSizeParams(pageIndex, pageSize),
|
||||
field: aggregationFieldName,
|
||||
order: sorting
|
||||
? {
|
||||
[sorting?.field === ExploreTableColumns.count ? '_count' : '_key']:
|
||||
sorting?.direction,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...getPaginationRequestParams(pageIndex, pageSize),
|
||||
},
|
||||
},
|
||||
filter: { term: { 'search.results.total_results': '0' } },
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
[ExploreTables.TopClicked]: {
|
||||
parseResponse: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
formula: {
|
||||
searches: { buckets: Array<{ doc_count: number; key: string }> };
|
||||
totalCount: { value: number };
|
||||
};
|
||||
};
|
||||
}>
|
||||
) => ({
|
||||
items: response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.count]: bucket.doc_count,
|
||||
[ExploreTableColumns.page]: bucket.key,
|
||||
})),
|
||||
totalCount: response.rawResponse.aggregations.formula.totalCount.value,
|
||||
}),
|
||||
requestParams: (
|
||||
{ timeRange, sorting, pageIndex, pageSize, search },
|
||||
aggregationFieldName = 'search.results.items.page.url'
|
||||
) =>
|
||||
getBaseSearchTemplate(
|
||||
aggregationFieldName,
|
||||
{ search, timeRange },
|
||||
{
|
||||
formula: {
|
||||
aggs: {
|
||||
...getTotalCountRequestParams(aggregationFieldName),
|
||||
searches: {
|
||||
terms: {
|
||||
...getPaginationRequestSizeParams(pageIndex, pageSize),
|
||||
field: aggregationFieldName,
|
||||
order: sorting
|
||||
? {
|
||||
[sorting.field === ExploreTableColumns.count ? '_count' : '_key']:
|
||||
sorting.direction,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...getPaginationRequestParams(pageIndex, pageSize),
|
||||
},
|
||||
},
|
||||
filter: { term: { 'event.action': 'search_click' } },
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
[ExploreTables.TopReferrers]: {
|
||||
parseResponse: (
|
||||
response: IKibanaSearchResponse<{
|
||||
aggregations: {
|
||||
formula: {
|
||||
searches: { buckets: Array<{ doc_count: number; key: string }> };
|
||||
totalCount: { value: number };
|
||||
};
|
||||
};
|
||||
}>
|
||||
) => ({
|
||||
items: response.rawResponse.aggregations.formula.searches.buckets.map((bucket) => ({
|
||||
[ExploreTableColumns.sessions]: bucket.doc_count,
|
||||
[ExploreTableColumns.page]: bucket.key,
|
||||
})),
|
||||
totalCount: response.rawResponse.aggregations.formula.totalCount.value,
|
||||
}),
|
||||
requestParams: (
|
||||
{ timeRange, sorting, pageIndex, pageSize, search },
|
||||
aggregationFieldName = 'page.referrer'
|
||||
) =>
|
||||
getBaseSearchTemplate(
|
||||
aggregationFieldName,
|
||||
{ search, timeRange },
|
||||
{
|
||||
formula: {
|
||||
aggs: {
|
||||
...getTotalCountRequestParams(aggregationFieldName),
|
||||
searches: {
|
||||
terms: {
|
||||
...getPaginationRequestSizeParams(pageIndex, pageSize),
|
||||
field: aggregationFieldName,
|
||||
order: sorting
|
||||
? {
|
||||
[sorting?.field === ExploreTableColumns.sessions ? '_count' : '_key']:
|
||||
sorting?.direction,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...getPaginationRequestParams(pageIndex, pageSize),
|
||||
},
|
||||
},
|
||||
filter: { term: { 'event.action': 'page_view' } },
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export interface AnalyticsCollectionExploreTableLogicValues {
|
||||
isLoading: boolean;
|
||||
items: ExploreTableItem[];
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
search: string;
|
||||
selectedTable: ExploreTables | null;
|
||||
sorting: Sorting | null;
|
||||
totalItemsCount: number;
|
||||
}
|
||||
|
||||
export interface AnalyticsCollectionExploreTableLogicActions {
|
||||
onTableChange(state: { page?: { index: number; size: number }; sort?: Sorting }): {
|
||||
page?: { index: number; size: number };
|
||||
sort?: Sorting;
|
||||
};
|
||||
reset(): void;
|
||||
setItems(items: ExploreTableItem[]): { items: ExploreTableItem[] };
|
||||
setSearch(search: string): { search: string };
|
||||
setSelectedTable(
|
||||
id: ExploreTables | null,
|
||||
sorting?: Sorting
|
||||
): { id: ExploreTables | null; sorting?: Sorting };
|
||||
setTotalItemsCount(count: number): { count: number };
|
||||
}
|
||||
|
||||
export const AnalyticsCollectionExploreTableLogic = kea<
|
||||
MakeLogicType<
|
||||
AnalyticsCollectionExploreTableLogicValues,
|
||||
AnalyticsCollectionExploreTableLogicActions
|
||||
>
|
||||
>({
|
||||
actions: {
|
||||
onTableChange: ({ page, sort }) => ({ page, sort }),
|
||||
reset: true,
|
||||
setItems: (items) => ({ items }),
|
||||
setSearch: (search) => ({ search }),
|
||||
setSelectedTable: (id, sorting) => ({ id, sorting }),
|
||||
setTotalItemsCount: (count) => ({ count }),
|
||||
},
|
||||
listeners: ({ actions, values }) => {
|
||||
const fetchItems = () => {
|
||||
if (values.selectedTable === null || !(values.selectedTable in tablesParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { requestParams, parseResponse } = tablesParams[values.selectedTable] as TableParams;
|
||||
const timeRange = AnalyticsCollectionToolbarLogic.values.timeRange;
|
||||
|
||||
const search$ = KibanaLogic.values.data.search
|
||||
.search(
|
||||
requestParams({
|
||||
pageIndex: values.pageIndex,
|
||||
pageSize: values.pageSize,
|
||||
search: values.search,
|
||||
sorting: values.sorting,
|
||||
timeRange,
|
||||
}),
|
||||
{
|
||||
indexPattern: AnalyticsCollectionDataViewLogic.values.dataView || undefined,
|
||||
sessionId: AnalyticsCollectionToolbarLogic.values.searchSessionId,
|
||||
}
|
||||
)
|
||||
.subscribe({
|
||||
error: (e) => {
|
||||
KibanaLogic.values.data.search.showError(e);
|
||||
},
|
||||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
const { items, totalCount } = parseResponse(response);
|
||||
|
||||
actions.setItems(items);
|
||||
actions.setTotalItemsCount(totalCount);
|
||||
search$.unsubscribe();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onTableChange: fetchItems,
|
||||
setSearch: fetchItems,
|
||||
setSelectedTable: fetchItems,
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: fetchItems,
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: fetchItems,
|
||||
};
|
||||
},
|
||||
path: ['enterprise_search', 'analytics', 'collections', 'explore', 'table'],
|
||||
reducers: () => ({
|
||||
isLoading: [
|
||||
false,
|
||||
{
|
||||
onTableChange: () => true,
|
||||
setItems: () => false,
|
||||
setSearch: () => true,
|
||||
setSelectedTable: () => true,
|
||||
setTableState: () => true,
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => true,
|
||||
[AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => true,
|
||||
},
|
||||
],
|
||||
items: [[], { setItems: (_, { items }) => items }],
|
||||
pageIndex: [
|
||||
0,
|
||||
{
|
||||
onTableChange: (_, { page }) => page?.index || 0,
|
||||
reset: () => 0,
|
||||
setSearch: () => 0,
|
||||
setSelectedTable: () => 0,
|
||||
},
|
||||
],
|
||||
pageSize: [
|
||||
BASE_PAGE_SIZE,
|
||||
{
|
||||
onTableChange: (_, { page }) => page?.size || BASE_PAGE_SIZE,
|
||||
reset: () => BASE_PAGE_SIZE,
|
||||
},
|
||||
],
|
||||
search: [
|
||||
'',
|
||||
{ reset: () => '', setSearch: (_, { search }) => search, setSelectedTable: () => '' },
|
||||
],
|
||||
selectedTable: [null, { setSelectedTable: (_, { id }) => id }],
|
||||
sorting: [
|
||||
null,
|
||||
{
|
||||
onTableChange: (_, { sort = null }) => sort,
|
||||
setSelectedTable: (_, { sorting = null }) => sorting,
|
||||
},
|
||||
],
|
||||
totalItemsCount: [0, { setTotalItemsCount: (_, { count }) => count }],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template';
|
||||
|
||||
import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar';
|
||||
|
||||
import { AnalyticsCollectionExplorer } from './analytics_collection_explorer';
|
||||
import { AnalyticsCollectionExplorerTable } from './analytics_collection_explorer_table';
|
||||
|
||||
describe('AnalyticsCollectionExplorer', () => {
|
||||
const mockValues = {
|
||||
analyticsCollection: { event_data_stream: 'test_data_stream', name: 'Mock Collection' },
|
||||
refreshInterval: { pause: false, value: 1000 },
|
||||
timeRange: { from: 'now-15m', to: 'now' },
|
||||
};
|
||||
const mockActions = { reset: jest.fn() };
|
||||
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
setMockValues(mockValues);
|
||||
setMockActions(mockActions);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders the AnalyticsCollectionExplorerTable', () => {
|
||||
const wrapper = shallow(<AnalyticsCollectionExplorer />);
|
||||
expect(wrapper.find(AnalyticsCollectionExplorerTable)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders the EnterpriseSearchAnalyticsPageTemplate', () => {
|
||||
const wrapper = shallow(<AnalyticsCollectionExplorer />);
|
||||
expect(wrapper.find(EnterpriseSearchAnalyticsPageTemplate)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('passes the expected props to EnterpriseSearchAnalyticsPageTemplate', () => {
|
||||
const wrapper = shallow(<AnalyticsCollectionExplorer />).find(
|
||||
EnterpriseSearchAnalyticsPageTemplate
|
||||
);
|
||||
|
||||
expect(wrapper.prop('pageChrome')).toEqual([mockValues.analyticsCollection.name]);
|
||||
expect(wrapper.prop('analyticsName')).toEqual(mockValues.analyticsCollection.name);
|
||||
expect(wrapper.prop('pageHeader')).toEqual({
|
||||
bottomBorder: false,
|
||||
pageTitle: 'Explorer',
|
||||
rightSideItems: [<AnalyticsCollectionToolbar />],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template';
|
||||
import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic';
|
||||
import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar';
|
||||
import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic';
|
||||
|
||||
import { AnalyticsCollectionExplorerTable } from './analytics_collection_explorer_table';
|
||||
|
||||
export const AnalyticsCollectionExplorer: React.FC = ({}) => {
|
||||
const { analyticsCollection } = useValues(FetchAnalyticsCollectionLogic);
|
||||
const { reset } = useActions(AnalyticsCollectionExploreTableLogic);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
reset();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EnterpriseSearchAnalyticsPageTemplate
|
||||
restrictWidth
|
||||
pageChrome={[analyticsCollection?.name]}
|
||||
analyticsName={analyticsCollection?.name}
|
||||
pageViewTelemetry={`View Analytics Collection - explorer`}
|
||||
pageHeader={{
|
||||
bottomBorder: false,
|
||||
pageTitle: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collectionsView.explorerView.title',
|
||||
{
|
||||
defaultMessage: 'Explorer',
|
||||
}
|
||||
),
|
||||
rightSideItems: [<AnalyticsCollectionToolbar />],
|
||||
}}
|
||||
>
|
||||
<AnalyticsCollectionExplorerTable />
|
||||
</EnterpriseSearchAnalyticsPageTemplate>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiButton, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
import { useDiscoverLink } from '../use_discover_link';
|
||||
|
||||
export const AnalyticsCollectionExplorerCallout: React.FC = () => {
|
||||
const { application } = useValues(KibanaLogic);
|
||||
const discoverLink = useDiscoverLink();
|
||||
|
||||
return discoverLink ? (
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collectionsView.explorer.callout.title',
|
||||
{ defaultMessage: 'Need a deeper analysis?' }
|
||||
)}
|
||||
iconType="inspect"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.explorer.callout.description"
|
||||
defaultMessage="Review your event logs in Discover to get more insights about your application metrics."
|
||||
/>
|
||||
</p>
|
||||
|
||||
<RedirectAppLinks coreStart={{ application }}>
|
||||
<EuiButton
|
||||
fill
|
||||
href={discoverLink}
|
||||
data-telemetry-id="entSearch-analytics-explorer-callout-exploreLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.explorer.callout.button"
|
||||
defaultMessage="Explore"
|
||||
/>
|
||||
</EuiButton>
|
||||
</RedirectAppLinks>
|
||||
</EuiCallOut>
|
||||
) : null;
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
import { ExploreTables } from '../analytics_collection_explore_table_types';
|
||||
|
||||
import { AnalyticsCollectionExplorerTable } from './analytics_collection_explorer_table';
|
||||
|
||||
describe('AnalyticsCollectionExplorerTable', () => {
|
||||
const mockActions = {
|
||||
onTableChange: jest.fn(),
|
||||
setPageIndex: jest.fn(),
|
||||
setPageSize: jest.fn(),
|
||||
setSearch: jest.fn(),
|
||||
setSelectedTable: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
setMockValues({ items: [], selectedTable: ExploreTables.TopClicked });
|
||||
setMockActions(mockActions);
|
||||
});
|
||||
|
||||
it('should set default selectedTable', () => {
|
||||
setMockValues({ items: [], selectedTable: null });
|
||||
const wrapper = mount(<AnalyticsCollectionExplorerTable />);
|
||||
|
||||
wrapper.update();
|
||||
|
||||
expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.SearchTerms, {
|
||||
direction: 'desc',
|
||||
field: 'count',
|
||||
});
|
||||
});
|
||||
|
||||
it('should call setSelectedTable when click on a tab', () => {
|
||||
const tabs = shallow(<AnalyticsCollectionExplorerTable />).find('EuiTab');
|
||||
|
||||
expect(tabs.length).toBe(4);
|
||||
|
||||
tabs.at(2).simulate('click');
|
||||
expect(mockActions.setSelectedTable).toHaveBeenCalledWith(ExploreTables.WorsePerformers, {
|
||||
direction: 'desc',
|
||||
field: 'count',
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onTableChange when table called onChange', () => {
|
||||
const table = shallow(<AnalyticsCollectionExplorerTable />).find('EuiBasicTable');
|
||||
|
||||
table.simulate('change', {
|
||||
page: { index: 23, size: 44 },
|
||||
sort: { direction: 'asc', field: 'test' },
|
||||
});
|
||||
expect(mockActions.onTableChange).toHaveBeenCalledWith({
|
||||
page: { index: 23, size: 44 },
|
||||
sort: { direction: 'asc', field: 'test' },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
Criteria,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiFieldSearch,
|
||||
EuiFlexGroup,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
EuiText,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
EuiTableFieldDataColumnType,
|
||||
EuiTableSortingType,
|
||||
} from '@elastic/eui/src/components/basic_table/table_types';
|
||||
import { UseEuiTheme } from '@elastic/eui/src/services/theme/hooks';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic';
|
||||
import {
|
||||
ExploreTableColumns,
|
||||
ExploreTableItem,
|
||||
ExploreTables,
|
||||
SearchTermsTable,
|
||||
TopClickedTable,
|
||||
TopReferrersTable,
|
||||
WorsePerformersTable,
|
||||
} from '../analytics_collection_explore_table_types';
|
||||
|
||||
import { AnalyticsCollectionExplorerCallout } from './analytics_collection_explorer_callout';
|
||||
|
||||
interface TableSetting<T = ExploreTableItem, K = T> {
|
||||
columns: Array<
|
||||
EuiBasicTableColumn<T & K> & {
|
||||
render?: (euiTheme: UseEuiTheme['euiTheme']) => EuiTableFieldDataColumnType<T & K>['render'];
|
||||
}
|
||||
>;
|
||||
sorting: EuiTableSortingType<T>;
|
||||
}
|
||||
|
||||
const tabs: Array<{ id: ExploreTables; name: string }> = [
|
||||
{
|
||||
id: ExploreTables.SearchTerms,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.searchTermsTab',
|
||||
{ defaultMessage: 'Search terms' }
|
||||
),
|
||||
},
|
||||
{
|
||||
id: ExploreTables.TopClicked,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.topClickedTab',
|
||||
{ defaultMessage: 'Top clicked results' }
|
||||
),
|
||||
},
|
||||
{
|
||||
id: ExploreTables.WorsePerformers,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.noResultsTab',
|
||||
{ defaultMessage: 'No results' }
|
||||
),
|
||||
},
|
||||
{
|
||||
id: ExploreTables.TopReferrers,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.explorer.referrersTab',
|
||||
{ defaultMessage: 'Referrers' }
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const tableSettings: {
|
||||
[ExploreTables.SearchTerms]: TableSetting<SearchTermsTable>;
|
||||
[ExploreTables.TopClicked]: TableSetting<TopClickedTable>;
|
||||
[ExploreTables.TopReferrers]: TableSetting<TopReferrersTable>;
|
||||
[ExploreTables.WorsePerformers]: TableSetting<WorsePerformersTable>;
|
||||
} = {
|
||||
[ExploreTables.SearchTerms]: {
|
||||
columns: [
|
||||
{
|
||||
field: ExploreTableColumns.searchTerms,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.searchTerms',
|
||||
{ defaultMessage: 'Search Terms' }
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: ExploreTableColumns.count,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.count',
|
||||
{ defaultMessage: 'Count' }
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
],
|
||||
sorting: {
|
||||
sort: {
|
||||
direction: 'desc',
|
||||
field: ExploreTableColumns.count,
|
||||
},
|
||||
},
|
||||
},
|
||||
[ExploreTables.WorsePerformers]: {
|
||||
columns: [
|
||||
{
|
||||
field: ExploreTableColumns.query,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.query',
|
||||
{ defaultMessage: 'Query' }
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: ExploreTableColumns.count,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.count',
|
||||
{ defaultMessage: 'Count' }
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
],
|
||||
sorting: {
|
||||
sort: {
|
||||
direction: 'desc',
|
||||
field: ExploreTableColumns.count,
|
||||
},
|
||||
},
|
||||
},
|
||||
[ExploreTables.TopClicked]: {
|
||||
columns: [
|
||||
{
|
||||
field: ExploreTableColumns.page,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.page',
|
||||
{ defaultMessage: 'Page' }
|
||||
),
|
||||
render: (euiTheme: UseEuiTheme['euiTheme']) => (value: string) =>
|
||||
(
|
||||
<EuiText size="s" color={euiTheme.colors.primaryText}>
|
||||
<p>{value}</p>
|
||||
</EuiText>
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: ExploreTableColumns.count,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.count',
|
||||
{ defaultMessage: 'Count' }
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
],
|
||||
sorting: {
|
||||
sort: {
|
||||
direction: 'desc',
|
||||
field: ExploreTableColumns.count,
|
||||
},
|
||||
},
|
||||
},
|
||||
[ExploreTables.TopReferrers]: {
|
||||
columns: [
|
||||
{
|
||||
field: ExploreTableColumns.page,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.page',
|
||||
{ defaultMessage: 'Page' }
|
||||
),
|
||||
render: (euiTheme: UseEuiTheme['euiTheme']) => (value: string) =>
|
||||
(
|
||||
<EuiText size="s" color={euiTheme.colors.primaryText}>
|
||||
<p>{value}</p>
|
||||
</EuiText>
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: ExploreTableColumns.sessions,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collections.collectionsView.exploreTable.session',
|
||||
{ defaultMessage: 'Session' }
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
],
|
||||
sorting: {
|
||||
sort: {
|
||||
direction: 'desc',
|
||||
field: ExploreTableColumns.sessions,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const AnalyticsCollectionExplorerTable = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { onTableChange, setSelectedTable, setSearch } = useActions(
|
||||
AnalyticsCollectionExploreTableLogic
|
||||
);
|
||||
const { items, isLoading, pageIndex, pageSize, search, selectedTable, sorting, totalItemsCount } =
|
||||
useValues(AnalyticsCollectionExploreTableLogic);
|
||||
let table = selectedTable !== null && (tableSettings[selectedTable] as TableSetting);
|
||||
if (table) {
|
||||
table = {
|
||||
...table,
|
||||
columns: table.columns.map((column) => ({
|
||||
...column,
|
||||
render: column.render?.(euiTheme),
|
||||
})) as TableSetting['columns'],
|
||||
sorting: { ...table.sorting, sort: sorting || undefined },
|
||||
};
|
||||
}
|
||||
const handleTableChange = ({ sort, page }: Criteria<ExploreTableItem>) => {
|
||||
onTableChange({ page, sort });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedTable) {
|
||||
const firstTabId = tabs[0].id;
|
||||
|
||||
setSelectedTable(firstTabId, (tableSettings[firstTabId] as TableSetting)?.sorting?.sort);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="l">
|
||||
<EuiTabs>
|
||||
{tabs?.map(({ id, name }) => (
|
||||
<EuiTab
|
||||
key={id}
|
||||
onClick={() => setSelectedTable(id, (tableSettings[id] as TableSetting)?.sorting?.sort)}
|
||||
isSelected={id === selectedTable}
|
||||
>
|
||||
{name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
|
||||
{table && (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFieldSearch
|
||||
placeholder={i18n.translate(
|
||||
'xpack.enterpriseSearch.analytics.collectionsView.explorer.searchPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Search',
|
||||
}
|
||||
)}
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
isClearable
|
||||
incremental
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiText size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.explorer.tableSummary"
|
||||
defaultMessage="Showing {items} of {totalItemsCount}"
|
||||
values={{
|
||||
items: (
|
||||
<strong>
|
||||
{pageSize * pageIndex + Number(!!items.length)}-
|
||||
{pageSize * pageIndex + items.length}
|
||||
</strong>
|
||||
),
|
||||
totalItemsCount,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
||||
<EuiBasicTable
|
||||
columns={table.columns}
|
||||
itemId={selectedTable || undefined}
|
||||
items={items}
|
||||
loading={isLoading}
|
||||
sorting={table.sorting}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
pageSizeOptions: [10, 20, 50],
|
||||
showPerPageOptions: true,
|
||||
totalItemCount: totalItemsCount,
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<AnalyticsCollectionExplorerCallout />
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -35,12 +35,9 @@ describe('AnalyticsCollectionChart', () => {
|
|||
from: moment().subtract(7, 'days').toISOString(),
|
||||
to: moment().toISOString(),
|
||||
};
|
||||
const mockedDataViewQuery = 'mockedDataViewQuery';
|
||||
|
||||
const defaultProps = {
|
||||
data: {},
|
||||
dataViewQuery: mockedDataViewQuery,
|
||||
id: 'mockedId',
|
||||
isLoading: false,
|
||||
selectedChart: FilterBy.Searches,
|
||||
setSelectedChart: jest.fn(),
|
||||
|
|
|
@ -42,8 +42,7 @@ const DEFAULT_STROKE_WIDTH = 1;
|
|||
const HOVER_STROKE_WIDTH = 3;
|
||||
const CHART_HEIGHT = 490;
|
||||
|
||||
interface AnalyticsCollectionChartProps extends WithLensDataInputProps {
|
||||
dataViewQuery: string;
|
||||
interface AnalyticsCollectionChartProps extends Pick<WithLensDataInputProps, 'timeRange'> {
|
||||
selectedChart: FilterBy | null;
|
||||
setSelectedChart(chart: FilterBy): void;
|
||||
}
|
||||
|
@ -303,6 +302,5 @@ export const AnalyticsCollectionChartWithLens = withLensData<
|
|||
visualizationType: 'lnsXY',
|
||||
};
|
||||
},
|
||||
getDataViewQuery: (props) => props.dataViewQuery,
|
||||
initialValues,
|
||||
});
|
||||
|
|
|
@ -54,7 +54,6 @@ const getMetricStatus = (metric: number): MetricStatus => {
|
|||
};
|
||||
|
||||
interface AnalyticsCollectionViewMetricProps {
|
||||
dataViewQuery: string;
|
||||
getFormula: (shift?: string) => string;
|
||||
isSelected?: boolean;
|
||||
name: string;
|
||||
|
@ -230,6 +229,5 @@ export const AnalyticsCollectionViewMetricWithLens = withLensData<
|
|||
visualizationType: 'lnsMetric',
|
||||
};
|
||||
},
|
||||
getDataViewQuery: (props) => props.dataViewQuery,
|
||||
initialValues,
|
||||
});
|
||||
|
|
|
@ -38,9 +38,9 @@ const mockValues = {
|
|||
};
|
||||
|
||||
const mockActions = {
|
||||
analyticsEventsExist: jest.fn(),
|
||||
fetchAnalyticsCollection: jest.fn(),
|
||||
fetchAnalyticsCollectionDataViewId: jest.fn(),
|
||||
analyticsEventsExist: jest.fn(),
|
||||
setTimeRange: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -92,7 +92,7 @@ describe('AnalyticsOverView', () => {
|
|||
);
|
||||
expect(wrapper?.find(AnalyticsCollectionChartWithLens)).toHaveLength(1);
|
||||
expect(wrapper?.find(AnalyticsCollectionChartWithLens).props()).toEqual({
|
||||
dataViewQuery: 'analytics-events-example',
|
||||
collection: mockValues.analyticsCollection,
|
||||
id: 'analytics-collection-chart-Analytics-Collection-1',
|
||||
searchSessionId: 'session-id',
|
||||
selectedChart: 'Searches',
|
||||
|
|
|
@ -18,14 +18,13 @@ import { FilterBy, getFormulaByFilter } from '../../../utils/get_formula_by_filt
|
|||
|
||||
import { EnterpriseSearchAnalyticsPageTemplate } from '../../layout/page_template';
|
||||
|
||||
import { AnalyticsCollectionExploreTable } from '../analytics_collection_explore_table/analytics_collection_explore_table';
|
||||
|
||||
import { AnalyticsCollectionNoEventsCallout } from '../analytics_collection_no_events_callout/analytics_collection_no_events_callout';
|
||||
import { AnalyticsCollectionToolbar } from '../analytics_collection_toolbar/analytics_collection_toolbar';
|
||||
import { AnalyticsCollectionToolbarLogic } from '../analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
||||
import { AnalyticsCollectionChartWithLens } from './analytics_collection_chart';
|
||||
import { AnalyticsCollectionViewMetricWithLens } from './analytics_collection_metric';
|
||||
import { AnalyticsCollectionOverviewTable } from './analytics_collection_overview_table';
|
||||
|
||||
const filters = [
|
||||
{
|
||||
|
@ -107,7 +106,7 @@ export const AnalyticsCollectionOverview: React.FC<AnalyticsCollectionOverviewPr
|
|||
|
||||
setFilterBy(id);
|
||||
}}
|
||||
dataViewQuery={analyticsCollection.events_datastream}
|
||||
collection={analyticsCollection}
|
||||
timeRange={timeRange}
|
||||
searchSessionId={searchSessionId}
|
||||
getFormula={getFormulaByFilter.bind(null, id)}
|
||||
|
@ -117,7 +116,7 @@ export const AnalyticsCollectionOverview: React.FC<AnalyticsCollectionOverviewPr
|
|||
|
||||
<AnalyticsCollectionChartWithLens
|
||||
id={'analytics-collection-chart-' + analyticsCollection.name}
|
||||
dataViewQuery={analyticsCollection.events_datastream}
|
||||
collection={analyticsCollection}
|
||||
timeRange={timeRange}
|
||||
setTimeRange={setTimeRange}
|
||||
searchSessionId={searchSessionId}
|
||||
|
@ -125,7 +124,7 @@ export const AnalyticsCollectionOverview: React.FC<AnalyticsCollectionOverviewPr
|
|||
setSelectedChart={setFilterBy}
|
||||
/>
|
||||
|
||||
<AnalyticsCollectionExploreTable filterBy={filterBy} />
|
||||
<AnalyticsCollectionOverviewTable filterBy={filterBy} />
|
||||
</EuiFlexGroup>
|
||||
</EnterpriseSearchAnalyticsPageTemplate>
|
||||
);
|
||||
|
|
|
@ -15,10 +15,11 @@ import { EuiBasicTable, EuiTab } from '@elastic/eui';
|
|||
|
||||
import { FilterBy } from '../../../utils/get_formula_by_filter';
|
||||
|
||||
import { AnalyticsCollectionExploreTable } from './analytics_collection_explore_table';
|
||||
import { ExploreTables } from './analytics_collection_explore_table_types';
|
||||
import { ExploreTables } from '../analytics_collection_explore_table_types';
|
||||
|
||||
describe('AnalyticsCollectionExploreTable', () => {
|
||||
import { AnalyticsCollectionOverviewTable } from './analytics_collection_overview_table';
|
||||
|
||||
describe('AnalyticsCollectionOverviewTable', () => {
|
||||
const mockValues = {
|
||||
activeTableId: 'search_terms',
|
||||
analyticsCollection: {
|
||||
|
@ -41,7 +42,7 @@ describe('AnalyticsCollectionExploreTable', () => {
|
|||
});
|
||||
|
||||
it('should call setSelectedTable with the correct table id when a tab is clicked', () => {
|
||||
const wrapper = shallow(<AnalyticsCollectionExploreTable filterBy={FilterBy.Sessions} />);
|
||||
const wrapper = shallow(<AnalyticsCollectionOverviewTable filterBy={FilterBy.Sessions} />);
|
||||
|
||||
const topReferrersTab = wrapper.find(EuiTab).at(0);
|
||||
topReferrersTab.simulate('click');
|
||||
|
@ -53,14 +54,9 @@ describe('AnalyticsCollectionExploreTable', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should call findDataView with the active table ID and search filter when mounted', () => {
|
||||
mount(<AnalyticsCollectionExploreTable filterBy={FilterBy.Sessions} />);
|
||||
expect(mockActions.findDataView).toHaveBeenCalledWith(mockValues.analyticsCollection);
|
||||
});
|
||||
|
||||
it('should render a table with the selectedTable', () => {
|
||||
setMockValues({ ...mockValues, selectedTable: ExploreTables.WorsePerformers });
|
||||
const wrapper = mount(<AnalyticsCollectionExploreTable filterBy={FilterBy.Sessions} />);
|
||||
const wrapper = mount(<AnalyticsCollectionOverviewTable filterBy={FilterBy.Sessions} />);
|
||||
expect(wrapper.find(EuiBasicTable).prop('itemId')).toBe(ExploreTables.WorsePerformers);
|
||||
});
|
||||
});
|
|
@ -33,9 +33,8 @@ import { generateEncodedPath } from '../../../../shared/encode_path_params';
|
|||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
import { COLLECTION_EXPLORER_PATH } from '../../../routes';
|
||||
import { FilterBy } from '../../../utils/get_formula_by_filter';
|
||||
import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic';
|
||||
|
||||
import { AnalyticsCollectionExploreTableLogic } from './analytics_collection_explore_table_logic';
|
||||
import { AnalyticsCollectionExploreTableLogic } from '../analytics_collection_explore_table_logic';
|
||||
import {
|
||||
ExploreTableColumns,
|
||||
ExploreTableItem,
|
||||
|
@ -44,7 +43,8 @@ import {
|
|||
TopClickedTable,
|
||||
TopReferrersTable,
|
||||
WorsePerformersTable,
|
||||
} from './analytics_collection_explore_table_types';
|
||||
} from '../analytics_collection_explore_table_types';
|
||||
import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic';
|
||||
|
||||
const tabsByFilter: Record<FilterBy, Array<{ id: ExploreTables; name: string }>> = {
|
||||
[FilterBy.Searches]: [
|
||||
|
@ -230,28 +230,22 @@ const tableSettings: {
|
|||
},
|
||||
};
|
||||
|
||||
interface AnalyticsCollectionExploreTableProps {
|
||||
interface AnalyticsCollectionOverviewTableProps {
|
||||
filterBy: FilterBy;
|
||||
}
|
||||
|
||||
export const AnalyticsCollectionExploreTable: React.FC<AnalyticsCollectionExploreTableProps> = ({
|
||||
export const AnalyticsCollectionOverviewTable: React.FC<AnalyticsCollectionOverviewTableProps> = ({
|
||||
filterBy,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { navigateToUrl } = useValues(KibanaLogic);
|
||||
const { analyticsCollection } = useValues(FetchAnalyticsCollectionLogic);
|
||||
const { findDataView, setSelectedTable, setSorting } = useActions(
|
||||
AnalyticsCollectionExploreTableLogic
|
||||
);
|
||||
const { onTableChange, setSelectedTable } = useActions(AnalyticsCollectionExploreTableLogic);
|
||||
const { items, isLoading, selectedTable, sorting } = useValues(
|
||||
AnalyticsCollectionExploreTableLogic
|
||||
);
|
||||
const tabs = tabsByFilter[filterBy];
|
||||
|
||||
useEffect(() => {
|
||||
findDataView(analyticsCollection);
|
||||
}, [analyticsCollection]);
|
||||
|
||||
useEffect(() => {
|
||||
const firstTableInTabsId = tabs[0].id;
|
||||
|
||||
|
@ -292,7 +286,7 @@ export const AnalyticsCollectionExploreTable: React.FC<AnalyticsCollectionExplor
|
|||
loading={isLoading}
|
||||
sorting={table.sorting}
|
||||
onChange={({ sort }) => {
|
||||
setSorting(sort);
|
||||
onTableChange({ sort });
|
||||
}}
|
||||
/>
|
||||
)
|
|
@ -37,7 +37,7 @@ describe('AnalyticsCollectionToolbar', () => {
|
|||
events_datastream: 'test-events',
|
||||
name: 'test',
|
||||
} as AnalyticsCollection,
|
||||
dataViewId: 'data-view-test',
|
||||
dataView: { id: 'data-view-test' },
|
||||
isLoading: false,
|
||||
refreshInterval: { pause: false, value: 10000 },
|
||||
timeRange: { from: 'now-90d', to: 'now' },
|
||||
|
@ -93,7 +93,9 @@ describe('AnalyticsCollectionToolbar', () => {
|
|||
|
||||
expect(exploreInDiscoverItem).toHaveLength(1);
|
||||
|
||||
expect(exploreInDiscoverItem.prop('href')).toBe("/app/discover#/?_a=(index:'data-view-test')");
|
||||
expect(exploreInDiscoverItem.prop('href')).toBe(
|
||||
"/app/discover#/?_a=(index:'data-view-test')&_g=(filters:!(),refreshInterval:(pause:!f,value:10000),time:(from:now-90d,to:now))"
|
||||
);
|
||||
});
|
||||
|
||||
it('should correct link to the manage datastream link', () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
|
@ -23,10 +23,12 @@ import {
|
|||
import { OnTimeChangeProps } from '@elastic/eui/src/components/date_picker/super_date_picker/super_date_picker';
|
||||
|
||||
import { OnRefreshChangeProps } from '@elastic/eui/src/components/date_picker/types';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { RedirectAppLinks } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||
|
||||
import { generateEncodedPath } from '../../../../shared/encode_path_params';
|
||||
|
||||
|
@ -34,6 +36,7 @@ import { KibanaLogic } from '../../../../shared/kibana';
|
|||
import { COLLECTION_INTEGRATE_PATH } from '../../../routes';
|
||||
import { DeleteAnalyticsCollectionLogic } from '../delete_analytics_collection_logic';
|
||||
import { FetchAnalyticsCollectionLogic } from '../fetch_analytics_collection_logic';
|
||||
import { useDiscoverLink } from '../use_discover_link';
|
||||
|
||||
import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar_logic';
|
||||
|
||||
|
@ -76,18 +79,16 @@ const defaultQuickRanges: EuiSuperDatePickerCommonRange[] = [
|
|||
];
|
||||
|
||||
export const AnalyticsCollectionToolbar: React.FC = () => {
|
||||
const discoverLink = useDiscoverLink();
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
const { application, navigateToUrl } = useValues(KibanaLogic);
|
||||
const { analyticsCollection } = useValues(FetchAnalyticsCollectionLogic);
|
||||
const { setTimeRange, setRefreshInterval, findDataViewId, onTimeRefresh } = useActions(
|
||||
const { setTimeRange, setRefreshInterval, onTimeRefresh } = useActions(
|
||||
AnalyticsCollectionToolbarLogic
|
||||
);
|
||||
const { refreshInterval, timeRange, dataViewId } = useValues(AnalyticsCollectionToolbarLogic);
|
||||
const { refreshInterval, timeRange } = useValues(AnalyticsCollectionToolbarLogic);
|
||||
const { deleteAnalyticsCollection } = useActions(DeleteAnalyticsCollectionLogic);
|
||||
const { isLoading } = useValues(DeleteAnalyticsCollectionLogic);
|
||||
const discoverUrl = application.getUrlForApp('discover', {
|
||||
path: `#/?_a=(index:'${dataViewId}')`,
|
||||
});
|
||||
const manageDatastreamUrl = application.getUrlForApp('management', {
|
||||
path: '/data/index_management/data_streams/' + analyticsCollection.events_datastream,
|
||||
});
|
||||
|
@ -104,10 +105,6 @@ export const AnalyticsCollectionToolbar: React.FC = () => {
|
|||
setRefreshInterval({ pause, value });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (analyticsCollection) findDataViewId(analyticsCollection);
|
||||
}, [analyticsCollection]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -126,41 +123,41 @@ export const AnalyticsCollectionToolbar: React.FC = () => {
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton iconType="arrowDown" iconSide="right" onClick={togglePopover}>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.manageButton"
|
||||
defaultMessage="Manage"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
anchorPosition="downRight"
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel>
|
||||
<EuiContextMenuItem
|
||||
icon="link"
|
||||
size="s"
|
||||
data-telemetry-id={'entSearch-analytics-overview-toolbar-integrate-tracker-link'}
|
||||
onClick={() =>
|
||||
navigateToUrl(
|
||||
generateEncodedPath(COLLECTION_INTEGRATE_PATH, {
|
||||
name: analyticsCollection.name,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.integrateTracker"
|
||||
defaultMessage="Integrate JS tracker"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
<RedirectAppLinks coreStart={{ application }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton iconType="arrowDown" iconSide="right" onClick={togglePopover}>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.manageButton"
|
||||
defaultMessage="Manage"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
anchorPosition="downRight"
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel>
|
||||
<EuiContextMenuItem
|
||||
icon="link"
|
||||
size="s"
|
||||
data-telemetry-id={'entSearch-analytics-overview-toolbar-integrate-tracker-link'}
|
||||
onClick={() =>
|
||||
navigateToUrl(
|
||||
generateEncodedPath(COLLECTION_INTEGRATE_PATH, {
|
||||
name: analyticsCollection.name,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.integrateTracker"
|
||||
defaultMessage="Integrate JS tracker"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
|
||||
<RedirectAppLinks application={application}>
|
||||
<EuiContextMenuItem
|
||||
icon="database"
|
||||
size="s"
|
||||
|
@ -173,41 +170,45 @@ export const AnalyticsCollectionToolbar: React.FC = () => {
|
|||
/>
|
||||
</EuiContextMenuItem>
|
||||
|
||||
<EuiContextMenuItem
|
||||
icon="visArea"
|
||||
href={discoverUrl}
|
||||
size="s"
|
||||
data-telemetry-id={'entSearch-analytics-overview-toolbar-manage-discover-link'}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.openInDiscover"
|
||||
defaultMessage="Create dashboards in Discover"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
</RedirectAppLinks>
|
||||
{discoverLink && (
|
||||
<EuiContextMenuItem
|
||||
icon="visArea"
|
||||
href={discoverLink}
|
||||
size="s"
|
||||
data-telemetry-id={'entSearch-analytics-overview-toolbar-manage-discover-link'}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collectionsView.openInDiscover"
|
||||
defaultMessage="Create dashboards in Discover"
|
||||
/>
|
||||
</EuiContextMenuItem>
|
||||
)}
|
||||
|
||||
<EuiPopoverFooter paddingSize="m">
|
||||
<EuiButton
|
||||
type="submit"
|
||||
color="danger"
|
||||
fullWidth
|
||||
isLoading={!isLoading}
|
||||
disabled={!isLoading}
|
||||
data-telemetry-id={'entSearch-analytics-overview-toolbar-delete-collection-button'}
|
||||
size="s"
|
||||
onClick={() => {
|
||||
deleteAnalyticsCollection(analyticsCollection.name);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collections.collectionsView.delete.buttonTitle"
|
||||
defaultMessage="Delete collection"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiPopoverFooter>
|
||||
</EuiContextMenuPanel>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
<EuiPopoverFooter paddingSize="m">
|
||||
<EuiButton
|
||||
type="submit"
|
||||
color="danger"
|
||||
fullWidth
|
||||
isLoading={!isLoading}
|
||||
disabled={!isLoading}
|
||||
data-telemetry-id={
|
||||
'entSearch-analytics-overview-toolbar-delete-collection-button'
|
||||
}
|
||||
size="s"
|
||||
onClick={() => {
|
||||
deleteAnalyticsCollection(analyticsCollection.name);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.analytics.collections.collectionsView.delete.buttonTitle"
|
||||
defaultMessage="Delete collection"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiPopoverFooter>
|
||||
</EuiContextMenuPanel>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</RedirectAppLinks>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -44,7 +44,6 @@ describe('AnalyticsCollectionToolbarLogic', () => {
|
|||
});
|
||||
const defaultProps: AnalyticsCollectionToolbarLogicValues = {
|
||||
_searchSessionId: null,
|
||||
dataViewId: null,
|
||||
refreshInterval: { pause: true, value: 10000 },
|
||||
searchSessionId: undefined,
|
||||
timeRange: { from: 'now-7d', to: 'now' },
|
||||
|
@ -62,11 +61,6 @@ describe('AnalyticsCollectionToolbarLogic', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('sets dataViewId', () => {
|
||||
AnalyticsCollectionToolbarLogic.actions.setDataViewId('sample_data_view_id');
|
||||
expect(AnalyticsCollectionToolbarLogic.values.dataViewId).toEqual('sample_data_view_id');
|
||||
});
|
||||
|
||||
it('sets refreshInterval', () => {
|
||||
const refreshInterval: RefreshInterval = { pause: false, value: 5000 };
|
||||
AnalyticsCollectionToolbarLogic.actions.setRefreshInterval(refreshInterval);
|
||||
|
@ -81,14 +75,6 @@ describe('AnalyticsCollectionToolbarLogic', () => {
|
|||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('should set dataViewId when findDataViewId called', async () => {
|
||||
await AnalyticsCollectionToolbarLogic.actions.findDataViewId({
|
||||
events_datastream: 'some-collection',
|
||||
name: 'some-collection-name',
|
||||
});
|
||||
expect(AnalyticsCollectionToolbarLogic.values.dataViewId).toBe('some-data-view-id');
|
||||
});
|
||||
|
||||
it('should set searchSessionId when onTimeRefresh called', () => {
|
||||
jest.spyOn(AnalyticsCollectionToolbarLogic.actions, 'setSearchSessionId');
|
||||
|
||||
|
|
|
@ -11,11 +11,9 @@ import { RefreshInterval } from '@kbn/data-plugin/common';
|
|||
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
|
||||
import { AnalyticsCollection } from '../../../../../../common/types/analytics';
|
||||
import { KibanaLogic } from '../../../../shared/kibana/kibana_logic';
|
||||
|
||||
export interface AnalyticsCollectionToolbarLogicActions {
|
||||
findDataViewId(collection: AnalyticsCollection): { collection: AnalyticsCollection };
|
||||
onTimeRefresh(): void;
|
||||
setDataViewId(id: string): { id: string };
|
||||
setRefreshInterval(refreshInterval: RefreshInterval): RefreshInterval;
|
||||
|
@ -27,7 +25,6 @@ export interface AnalyticsCollectionToolbarLogicActions {
|
|||
export interface AnalyticsCollectionToolbarLogicValues {
|
||||
// kea forbid to set undefined as a value
|
||||
_searchSessionId: string | null;
|
||||
dataViewId: string | null;
|
||||
refreshInterval: RefreshInterval;
|
||||
searchSessionId: string | undefined;
|
||||
timeRange: TimeRange;
|
||||
|
@ -40,23 +37,12 @@ export const AnalyticsCollectionToolbarLogic = kea<
|
|||
MakeLogicType<AnalyticsCollectionToolbarLogicValues, AnalyticsCollectionToolbarLogicActions>
|
||||
>({
|
||||
actions: {
|
||||
findDataViewId: (collection) => ({ collection }),
|
||||
onTimeRefresh: true,
|
||||
setDataViewId: (id) => ({ id }),
|
||||
setRefreshInterval: ({ pause, value }) => ({ pause, value }),
|
||||
setSearchSessionId: (searchSessionId) => ({ searchSessionId }),
|
||||
setTimeRange: ({ from, to }) => ({ from, to }),
|
||||
},
|
||||
listeners: ({ actions }) => ({
|
||||
findDataViewId: async ({ collection }) => {
|
||||
const dataViewId = (
|
||||
await KibanaLogic.values.data.dataViews.find(collection.events_datastream, 1)
|
||||
)?.[0]?.id;
|
||||
|
||||
if (dataViewId) {
|
||||
actions.setDataViewId(dataViewId);
|
||||
}
|
||||
},
|
||||
onTimeRefresh() {
|
||||
actions.setSearchSessionId(KibanaLogic.values.data.search.session.start());
|
||||
},
|
||||
|
@ -75,7 +61,6 @@ export const AnalyticsCollectionToolbarLogic = kea<
|
|||
null,
|
||||
{ setSearchSessionId: (state, { searchSessionId }) => searchSessionId },
|
||||
],
|
||||
dataViewId: [null, { setDataViewId: (_, { id }) => id }],
|
||||
refreshInterval: [
|
||||
DEFAULT_REFRESH_INTERVAL,
|
||||
{
|
||||
|
|
|
@ -25,6 +25,10 @@ import { AddAnalyticsCollection } from '../add_analytics_collections/add_analyti
|
|||
|
||||
import { EnterpriseSearchAnalyticsPageTemplate } from '../layout/page_template';
|
||||
|
||||
import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic';
|
||||
|
||||
import { AnalyticsCollectionExplorer } from './analytics_collection_explorer/analytics_collection_explorer';
|
||||
|
||||
import { AnalyticsCollectionIntegrateView } from './analytics_collection_integrate/analytics_collection_integrate_view';
|
||||
import { AnalyticsCollectionOverview } from './analytics_collection_overview/analytics_collection_overview';
|
||||
import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
@ -33,6 +37,7 @@ import { FetchAnalyticsCollectionLogic } from './fetch_analytics_collection_logi
|
|||
|
||||
export const AnalyticsCollectionView: React.FC = () => {
|
||||
useMountedLogic(AnalyticsCollectionToolbarLogic);
|
||||
useMountedLogic(AnalyticsCollectionDataViewLogic);
|
||||
const { fetchAnalyticsCollection } = useActions(FetchAnalyticsCollectionLogic);
|
||||
const { analyticsCollection, isLoading } = useValues(FetchAnalyticsCollectionLogic);
|
||||
const { name } = useParams<{ name: string }>();
|
||||
|
@ -52,7 +57,9 @@ export const AnalyticsCollectionView: React.FC = () => {
|
|||
<AnalyticsCollectionIntegrateView analyticsCollection={analyticsCollection} />
|
||||
</Route>
|
||||
|
||||
<Route exact path={COLLECTION_EXPLORER_PATH} />
|
||||
<Route exact path={COLLECTION_EXPLORER_PATH}>
|
||||
<AnalyticsCollectionExplorer />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '../../api/fetch_analytics_collection/fetch_analytics_collection_api_logic';
|
||||
|
||||
export interface FetchAnalyticsCollectionActions {
|
||||
apiSuccess: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['apiSuccess'];
|
||||
fetchAnalyticsCollection(name: string): AnalyticsCollection;
|
||||
makeRequest: Actions<{}, FetchAnalyticsCollectionApiLogicResponse>['makeRequest'];
|
||||
}
|
||||
|
@ -33,7 +34,7 @@ export const FetchAnalyticsCollectionLogic = kea<
|
|||
fetchAnalyticsCollection: (name) => ({ name }),
|
||||
},
|
||||
connect: {
|
||||
actions: [FetchAnalyticsCollectionAPILogic, ['makeRequest']],
|
||||
actions: [FetchAnalyticsCollectionAPILogic, ['makeRequest', 'apiSuccess']],
|
||||
values: [FetchAnalyticsCollectionAPILogic, ['data', 'status']],
|
||||
},
|
||||
listeners: ({ actions }) => ({
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
|
||||
import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic';
|
||||
import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic';
|
||||
|
||||
export const useDiscoverLink = (): string | null => {
|
||||
const { application } = useValues(KibanaLogic);
|
||||
const { dataView } = useValues(AnalyticsCollectionDataViewLogic);
|
||||
const { refreshInterval, timeRange } = useValues(AnalyticsCollectionToolbarLogic);
|
||||
|
||||
return dataView
|
||||
? application.getUrlForApp('discover', {
|
||||
path: `#/?_a=(index:'${
|
||||
dataView.id
|
||||
}')&_g=(filters:!(),refreshInterval:(pause:!${refreshInterval.pause
|
||||
.toString()
|
||||
.charAt(0)},value:${refreshInterval.value}),time:(from:${timeRange.from},to:${
|
||||
timeRange.to
|
||||
}))`,
|
||||
})
|
||||
: null;
|
||||
};
|
|
@ -93,7 +93,6 @@ const getChartStatus = (metric: number | null): ChartStatus => {
|
|||
if (metric && metric < 0) return ChartStatus.DECREASE;
|
||||
return ChartStatus.CONSTANT;
|
||||
};
|
||||
|
||||
export const AnalyticsCollectionCard: React.FC<
|
||||
AnalyticsCollectionCardProps & AnalyticsCollectionCardLensProps
|
||||
> = ({ collection, isLoading, isCreatedByEngine, subtitle, data, metric, secondaryMetric }) => {
|
||||
|
@ -332,6 +331,5 @@ export const AnalyticsCollectionCardWithLens = withLensData<
|
|||
visualizationType: 'lnsMetric',
|
||||
};
|
||||
},
|
||||
getDataViewQuery: ({ collection }) => collection.events_datastream,
|
||||
initialValues,
|
||||
});
|
||||
|
|
|
@ -13,10 +13,10 @@ import { mount, shallow } from 'enzyme';
|
|||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { FormulaPublicApi } from '@kbn/lens-plugin/public';
|
||||
|
||||
import { findOrCreateDataView } from '../utils/find_or_create_data_view';
|
||||
|
||||
import { withLensData } from './with_lens_data';
|
||||
|
||||
interface MockComponentProps {
|
||||
|
@ -29,6 +29,20 @@ interface MockComponentLensProps {
|
|||
|
||||
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
const mockCollection = {
|
||||
event_retention_day_length: 180,
|
||||
events_datastream: 'analytics-events-example2',
|
||||
id: 'example2',
|
||||
name: 'example2',
|
||||
};
|
||||
const mockDataView = { id: 'test-data-view-id' };
|
||||
|
||||
jest.mock('../utils/find_or_create_data_view', () => {
|
||||
return {
|
||||
findOrCreateDataView: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('withLensData', () => {
|
||||
const MockComponent: React.FC<MockComponentProps> = ({ name }) => <div>{name}</div>;
|
||||
|
||||
|
@ -48,85 +62,91 @@ describe('withLensData', () => {
|
|||
return { data: 'initial data' };
|
||||
}),
|
||||
getAttributes: jest.fn(),
|
||||
getDataViewQuery: jest.fn(),
|
||||
initialValues: { data: 'initial data' },
|
||||
}
|
||||
);
|
||||
|
||||
const props = { name: 'John Doe' };
|
||||
const props = { collection: mockCollection, name: 'John Doe' };
|
||||
const wrapper = shallow(
|
||||
<WrappedComponent id={'id'} timeRange={{ from: 'now-10d', to: 'now' }} {...props} />
|
||||
);
|
||||
expect(wrapper.find(MockComponent).prop('data')).toEqual('initial data');
|
||||
});
|
||||
|
||||
it('should call getDataViewQuery with props', async () => {
|
||||
const getDataViewQuery = jest.fn();
|
||||
getDataViewQuery.mockReturnValue('title-collection');
|
||||
const findMock = jest.spyOn(mockKibanaValues.data.dataViews, 'find').mockResolvedValueOnce([]);
|
||||
it('should call findOrCreateDataView with collection', async () => {
|
||||
const WrappedComponent = withLensData<MockComponentProps, MockComponentLensProps>(
|
||||
MockComponent,
|
||||
{
|
||||
dataLoadTransform: jest.fn(),
|
||||
getAttributes: jest.fn(),
|
||||
getDataViewQuery,
|
||||
initialValues: { data: 'initial data' },
|
||||
}
|
||||
);
|
||||
|
||||
const props = { id: 'id', name: 'John Doe', timeRange: { from: 'now-10d', to: 'now' } };
|
||||
const props = {
|
||||
collection: mockCollection,
|
||||
id: 'id',
|
||||
name: 'John Doe',
|
||||
timeRange: { from: 'now-10d', to: 'now' },
|
||||
};
|
||||
mount(<WrappedComponent {...props} />);
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(getDataViewQuery).toHaveBeenCalledWith(props);
|
||||
expect(findMock).toHaveBeenCalledWith('title-collection', 1);
|
||||
expect(findOrCreateDataView).toHaveBeenCalledWith(mockCollection);
|
||||
});
|
||||
|
||||
it('should call getAttributes with the correct arguments when dataView and formula are available', async () => {
|
||||
const getAttributes = jest.fn();
|
||||
const dataView = {} as DataView;
|
||||
const formula = {} as FormulaPublicApi;
|
||||
mockKibanaValues.lens.stateHelperApi = jest.fn().mockResolvedValueOnce({ formula });
|
||||
jest.spyOn(mockKibanaValues.data.dataViews, 'find').mockResolvedValueOnce([dataView]);
|
||||
(findOrCreateDataView as jest.Mock).mockResolvedValueOnce(mockDataView);
|
||||
|
||||
const WrappedComponent = withLensData<MockComponentProps, MockComponentLensProps>(
|
||||
MockComponent,
|
||||
{
|
||||
dataLoadTransform: jest.fn(),
|
||||
getAttributes,
|
||||
getDataViewQuery: jest.fn(),
|
||||
initialValues: { data: 'initial data' },
|
||||
}
|
||||
);
|
||||
|
||||
const props = { id: 'id', name: 'John Doe', timeRange: { from: 'now-10d', to: 'now' } };
|
||||
const props = {
|
||||
collection: mockCollection,
|
||||
id: 'id',
|
||||
name: 'John Doe',
|
||||
timeRange: { from: 'now-10d', to: 'now' },
|
||||
};
|
||||
mount(<WrappedComponent {...props} />);
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
expect(getAttributes).toHaveBeenCalledWith(dataView, formula, props);
|
||||
expect(getAttributes).toHaveBeenCalledWith(mockDataView, formula, props);
|
||||
});
|
||||
|
||||
it('should not call getAttributes when dataView is not available', async () => {
|
||||
const getAttributes = jest.fn();
|
||||
const formula = {} as FormulaPublicApi;
|
||||
mockKibanaValues.lens.stateHelperApi = jest.fn().mockResolvedValueOnce({ formula });
|
||||
jest.spyOn(mockKibanaValues.data.dataViews, 'find').mockResolvedValueOnce([]);
|
||||
(findOrCreateDataView as jest.Mock).mockResolvedValueOnce(undefined);
|
||||
|
||||
const WrappedComponent = withLensData<MockComponentProps, MockComponentLensProps>(
|
||||
MockComponent,
|
||||
{
|
||||
dataLoadTransform: jest.fn(),
|
||||
getAttributes,
|
||||
getDataViewQuery: jest.fn(),
|
||||
initialValues: { data: 'initial data' },
|
||||
}
|
||||
);
|
||||
|
||||
const props = { id: 'id', name: 'John Doe', timeRange: { from: 'now-10d', to: 'now' } };
|
||||
const props = {
|
||||
collection: mockCollection,
|
||||
id: 'id',
|
||||
name: 'John Doe',
|
||||
timeRange: { from: 'now-10d', to: 'now' },
|
||||
};
|
||||
mount(<WrappedComponent {...props} />);
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
|
|
|
@ -17,9 +17,13 @@ import { TimeRange } from '@kbn/es-query';
|
|||
import { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common';
|
||||
import { FormulaPublicApi, TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||
|
||||
import { AnalyticsCollection } from '../../../../common/types/analytics';
|
||||
|
||||
import { KibanaLogic } from '../../shared/kibana';
|
||||
import { findOrCreateDataView } from '../utils/find_or_create_data_view';
|
||||
|
||||
export interface WithLensDataInputProps {
|
||||
collection: AnalyticsCollection;
|
||||
id: string;
|
||||
searchSessionId?: string;
|
||||
setTimeRange?(timeRange: TimeRange): void;
|
||||
|
@ -36,7 +40,6 @@ interface WithLensDataParams<Props, OutputState> {
|
|||
formulaApi: FormulaPublicApi,
|
||||
props: Props
|
||||
) => TypedLensByValueInput['attributes'];
|
||||
getDataViewQuery: (props: Props) => string;
|
||||
initialValues: OutputState;
|
||||
}
|
||||
|
||||
|
@ -45,14 +48,12 @@ export const withLensData = <T extends {} = {}, OutputState extends {} = {}>(
|
|||
{
|
||||
dataLoadTransform,
|
||||
getAttributes,
|
||||
getDataViewQuery,
|
||||
initialValues,
|
||||
}: WithLensDataParams<Omit<T, keyof OutputState>, OutputState>
|
||||
) => {
|
||||
const ComponentWithLensData: React.FC<T & WithLensDataInputProps> = (props) => {
|
||||
const {
|
||||
lens: { EmbeddableComponent, stateHelperApi },
|
||||
data: { dataViews },
|
||||
} = useValues(KibanaLogic);
|
||||
const [dataView, setDataView] = useState<DataView | null>(null);
|
||||
const [data, setData] = useState<OutputState>(initialValues);
|
||||
|
@ -73,11 +74,7 @@ export const withLensData = <T extends {} = {}, OutputState extends {} = {}>(
|
|||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const [target] = await dataViews.find(getDataViewQuery(props), 1);
|
||||
|
||||
if (target) {
|
||||
setDataView(target);
|
||||
}
|
||||
setDataView(await findOrCreateDataView(props.collection));
|
||||
})();
|
||||
}, [props]);
|
||||
useEffect(() => {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
|
||||
import { AnalyticsCollection } from '../../../../common/types/analytics';
|
||||
import { KibanaLogic } from '../../shared/kibana/kibana_logic';
|
||||
|
||||
import { findOrCreateDataView } from './find_or_create_data_view';
|
||||
|
||||
jest.mock('../../shared/kibana/kibana_logic', () => ({
|
||||
KibanaLogic: {
|
||||
values: {
|
||||
data: {
|
||||
dataViews: {
|
||||
createAndSave: jest.fn(),
|
||||
find: jest.fn(() => Promise.resolve([])),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('findOrCreateDataView', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should find and set dataView when analytics collection fetched', async () => {
|
||||
const dataView = { id: 'test', title: 'events1' } as DataView;
|
||||
jest.spyOn(KibanaLogic.values.data.dataViews, 'find').mockResolvedValueOnce([dataView]);
|
||||
|
||||
expect(
|
||||
await findOrCreateDataView({
|
||||
events_datastream: 'events1',
|
||||
name: 'collection1',
|
||||
} as AnalyticsCollection)
|
||||
).toEqual(dataView);
|
||||
expect(KibanaLogic.values.data.dataViews.createAndSave).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should create, save and set dataView when analytics collection fetched but dataView is not found', async () => {
|
||||
const dataView = { id: 'test21' } as DataView;
|
||||
jest.spyOn(KibanaLogic.values.data.dataViews, 'createAndSave').mockResolvedValueOnce(dataView);
|
||||
|
||||
expect(
|
||||
await findOrCreateDataView({
|
||||
events_datastream: 'events1',
|
||||
name: 'collection1',
|
||||
} as AnalyticsCollection)
|
||||
).toEqual(dataView);
|
||||
expect(KibanaLogic.values.data.dataViews.createAndSave).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AnalyticsCollection } from '../../../../common/types/analytics';
|
||||
import { KibanaLogic } from '../../shared/kibana/kibana_logic';
|
||||
|
||||
export const findOrCreateDataView = async (collection: AnalyticsCollection) => {
|
||||
const dataView = (
|
||||
await KibanaLogic.values.data.dataViews.find(collection.events_datastream, 1)
|
||||
).find((result) => result.title === collection.events_datastream);
|
||||
|
||||
if (dataView) {
|
||||
return dataView;
|
||||
}
|
||||
|
||||
return await KibanaLogic.values.data.dataViews.createAndSave(
|
||||
{
|
||||
allowNoIndex: true,
|
||||
name: `behavioral_analytics.events-${collection.name}`,
|
||||
timeFieldName: '@timestamp',
|
||||
title: collection.events_datastream,
|
||||
},
|
||||
true
|
||||
);
|
||||
};
|
|
@ -58,5 +58,6 @@
|
|||
"@kbn/react-field",
|
||||
"@kbn/field-types",
|
||||
"@kbn/core-elasticsearch-server-mocks",
|
||||
"@kbn/shared-ux-link-redirect-app",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue