Skip to content

Commit

Permalink
[Enterprise Search] [Behavioral analytics] Add search to collection l…
Browse files Browse the repository at this point in the history
…ist (elastic#155524)

✔️  Implement search for collection list Page
✔️ Add search query to fetch collection request
✔️ Update server-side to support new search query
<img width="1162" alt="image"
src="https://user-images.githubusercontent.com/17390745/233668294-0b3dd60b-a016-43dd-8362-91a368212d5c.png">

---------

Co-authored-by: Joseph McElroy <joseph.mcelroy@elastic.co>
  • Loading branch information
2 people authored and nikitaindik committed Apr 25, 2023
1 parent a6c2e08 commit 699a03a
Show file tree
Hide file tree
Showing 16 changed files with 395 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ describe('FetchAnalyticsCollectionsApiLogic', () => {
it('calls the analytics collections list api', async () => {
const promise = Promise.resolve([{ name: 'result' }]);
http.get.mockReturnValue(promise);
const result = fetchAnalyticsCollections();
const result = fetchAnalyticsCollections({});
await nextTick();
expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/analytics/collections');
expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/analytics/collections', {
query: { query: '' },
});
await expect(result).resolves.toEqual([{ name: 'result' }]);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ import { HttpLogic } from '../../../shared/http';

export type FetchAnalyticsCollectionsApiLogicResponse = AnalyticsCollection[];

export const fetchAnalyticsCollections = async () => {
interface FetchAnalyticsCollectionsApiLogicArgs {
query?: string;
}

export const fetchAnalyticsCollections = async ({
query = '',
}: FetchAnalyticsCollectionsApiLogicArgs) => {
const { http } = HttpLogic.values;
const route = '/internal/enterprise_search/analytics/collections';
const response = await http.get<FetchAnalyticsCollectionsApiLogicResponse>(route);
const response = await http.get<FetchAnalyticsCollectionsApiLogicResponse>(route, {
query: {
query,
},
});

return response;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
FetchAnalyticsCollectionLogic,
} from './fetch_analytics_collection_logic';

interface AnalyticsCollectionDataViewLogicValues {
export interface AnalyticsCollectionDataViewLogicValues {
dataView: DataView | null;
}

Expand All @@ -39,7 +39,7 @@ export const AnalyticsCollectionDataViewLogic = kea<
actions.setDataView(await findOrCreateDataView(collection));
},
}),
path: ['enterprise_search', 'analytics', 'collections', 'dataView'],
path: ['enterprise_search', 'analytics', 'collection', 'dataView'],
reducers: () => ({
dataView: [null, { setDataView: (_, { dataView }) => dataView }],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import { LogicMounter } from '../../../__mocks__/kea_logic';

import { nextTick } from '@kbn/test-jest-helpers';

import { KibanaLogic } from '../../../shared/kibana/kibana_logic';

import {
Expand Down Expand Up @@ -38,13 +40,18 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => {
});

const defaultProps = {
dataView: null,
isLoading: false,
items: [],
pageIndex: 0,
pageSize: 10,
search: '',
selectedTable: null,
sorting: null,
timeRange: {
from: 'now-7d',
to: 'now',
},
totalItemsCount: 0,
};

Expand Down Expand Up @@ -79,6 +86,10 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => {
});

describe('isLoading', () => {
beforeEach(() => {
mount({ selectedTable: ExploreTables.TopReferrers });
});

it('should handle onTableChange', () => {
AnalyticsCollectionExploreTableLogic.actions.onTableChange({
page: { index: 2, size: 10 },
Expand Down Expand Up @@ -241,11 +252,15 @@ describe('AnalyticsCollectionExplorerTablesLogic', () => {
});
});

it('should fetch items when search changes', () => {
it('should fetch items when search changes', async () => {
jest.useFakeTimers({ legacyFakeTimers: true });
AnalyticsCollectionExploreTableLogic.actions.setSelectedTable(ExploreTables.WorsePerformers);
(KibanaLogic.values.data.search.search as jest.Mock).mockClear();

AnalyticsCollectionExploreTableLogic.actions.setSearch('test');
jest.advanceTimersByTime(200);
await nextTick();

expect(KibanaLogic.values.data.search.search).toHaveBeenCalledWith(expect.any(Object), {
indexPattern: undefined,
sessionId: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {

import { KibanaLogic } from '../../../shared/kibana/kibana_logic';

import { AnalyticsCollectionDataViewLogic } from './analytics_collection_data_view_logic';
import {
AnalyticsCollectionDataViewLogic,
AnalyticsCollectionDataViewLogicValues,
} from './analytics_collection_data_view_logic';

import {
getBaseSearchTemplate,
Expand All @@ -33,9 +36,13 @@ import {
TopReferrersTable,
WorsePerformersTable,
} from './analytics_collection_explore_table_types';
import { AnalyticsCollectionToolbarLogic } from './analytics_collection_toolbar/analytics_collection_toolbar_logic';
import {
AnalyticsCollectionToolbarLogic,
AnalyticsCollectionToolbarLogicValues,
} from './analytics_collection_toolbar/analytics_collection_toolbar_logic';

const BASE_PAGE_SIZE = 10;
const SEARCH_COOLDOWN = 200;

export interface Sorting<T extends ExploreTableItem = ExploreTableItem> {
direction: 'asc' | 'desc';
Expand Down Expand Up @@ -243,13 +250,16 @@ const tablesParams: {
};

export interface AnalyticsCollectionExploreTableLogicValues {
dataView: AnalyticsCollectionDataViewLogicValues['dataView'];
isLoading: boolean;
items: ExploreTableItem[];
pageIndex: number;
pageSize: number;
search: string;
searchSessionId: AnalyticsCollectionToolbarLogicValues['searchSessionId'];
selectedTable: ExploreTables | null;
sorting: Sorting | null;
timeRange: AnalyticsCollectionToolbarLogicValues['timeRange'];
totalItemsCount: number;
}

Expand Down Expand Up @@ -282,14 +292,26 @@ export const AnalyticsCollectionExploreTableLogic = kea<
setSelectedTable: (id, sorting) => ({ id, sorting }),
setTotalItemsCount: (count) => ({ count }),
},
connect: {
actions: [AnalyticsCollectionToolbarLogic, ['setTimeRange', 'setSearchSessionId']],
values: [
AnalyticsCollectionDataViewLogic,
['dataView'],
AnalyticsCollectionToolbarLogic,
['timeRange', 'searchSessionId'],
],
},
listeners: ({ actions, values }) => {
const fetchItems = () => {
if (values.selectedTable === null || !(values.selectedTable in tablesParams)) {
actions.setItems([]);
actions.setTotalItemsCount(0);

return;
}

const { requestParams, parseResponse } = tablesParams[values.selectedTable] as TableParams;
const timeRange = AnalyticsCollectionToolbarLogic.values.timeRange;
const timeRange = values.timeRange;

const search$ = KibanaLogic.values.data.search
.search(
Expand All @@ -301,8 +323,8 @@ export const AnalyticsCollectionExploreTableLogic = kea<
timeRange,
}),
{
indexPattern: AnalyticsCollectionDataViewLogic.values.dataView || undefined,
sessionId: AnalyticsCollectionToolbarLogic.values.searchSessionId,
indexPattern: values.dataView || undefined,
sessionId: values.searchSessionId,
}
)
.subscribe({
Expand All @@ -323,24 +345,27 @@ export const AnalyticsCollectionExploreTableLogic = kea<

return {
onTableChange: fetchItems,
setSearch: fetchItems,
setSearch: async (_, breakpoint) => {
await breakpoint(SEARCH_COOLDOWN);
fetchItems();
},
setSearchSessionId: fetchItems,
setSelectedTable: fetchItems,
[AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: fetchItems,
[AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: fetchItems,
setTimeRange: fetchItems,
};
},
path: ['enterprise_search', 'analytics', 'collections', 'explore', 'table'],
path: ['enterprise_search', 'analytics', 'collection', 'explore', 'table'],
reducers: () => ({
isLoading: [
false,
{
onTableChange: () => true,
setItems: () => false,
setSearch: () => true,
setSearchSessionId: () => true,
setSelectedTable: () => true,
setTableState: () => true,
[AnalyticsCollectionToolbarLogic.actionTypes.setTimeRange]: () => true,
[AnalyticsCollectionToolbarLogic.actionTypes.setSearchSessionId]: () => true,
setTimeRange: () => true,
},
],
items: [[], { setItems: (_, { items }) => items }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ export const AnalyticsCollectionExplorerTable = () => {
value={search}
onChange={(event) => setSearch(event.target.value)}
isClearable
isLoading={isLoading}
incremental
fullWidth
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const AnalyticsCollectionToolbarLogic = kea<
actions.setSearchSessionId(null);
},
}),
path: ['enterprise_search', 'analytics', 'collections', 'toolbar'],
path: ['enterprise_search', 'analytics', 'collection', 'toolbar'],
reducers: () => ({
_searchSessionId: [
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const DeleteAnalyticsCollectionLogic = kea<
actions.makeRequest({ name });
},
}),
path: ['enterprise_search', 'analytics', 'collections', 'delete'],
path: ['enterprise_search', 'analytics', 'collection', 'delete'],
selectors: ({ selectors }) => ({
isLoading: [
() => [selectors.status],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { EuiEmptyPrompt, EuiImage } from '@elastic/eui';

import { i18n } from '@kbn/i18n';

import noMlModelsGraphicDark from '../../../../assets/images/no_ml_models_dark.svg';

const ICON_WIDTH = 294;

interface AnalyticsCollectionNotFoundProps {
query: string;
}

export const AnalyticsCollectionNotFound: React.FC<AnalyticsCollectionNotFoundProps> = ({
query,
}) => (
<EuiEmptyPrompt
icon={<EuiImage size={ICON_WIDTH} src={noMlModelsGraphicDark} alt="icon" />}
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.analytics.collections.notFound.headingTitle', {
defaultMessage: 'No results found for “{query}”',
values: { query },
})}
</h2>
}
body={
<p>
{i18n.translate('xpack.enterpriseSearch.analytics.collections.notFound.subHeading', {
defaultMessage: 'Try searching for another term.',
})}
</p>
}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { EuiButtonGroup, EuiSuperDatePicker } from '@elastic/eui';
import { AnalyticsCollection } from '../../../../../common/types/analytics';

import { AnalyticsCollectionCardWithLens } from './analytics_collection_card/analytics_collection_card';
import { AnalyticsCollectionNotFound } from './analytics_collection_not_found';

import { AnalyticsCollectionTable } from './analytics_collection_table';

Expand All @@ -30,36 +31,43 @@ describe('AnalyticsCollectionTable', () => {
name: 'example2',
},
];
const props = {
collections: analyticsCollections,
isSearching: false,
onSearch: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it('renders cards', () => {
const wrapper = shallow(<AnalyticsCollectionTable collections={analyticsCollections} />);
const wrapper = shallow(<AnalyticsCollectionTable {...props} />);
const collectionCards = wrapper.find(AnalyticsCollectionCardWithLens);

expect(collectionCards).toHaveLength(analyticsCollections.length);
expect(collectionCards.at(1).prop('collection')).toMatchObject(analyticsCollections[1]);
});

it('renders filters', () => {
const buttonGroup = shallow(
<AnalyticsCollectionTable collections={analyticsCollections} />
).find(EuiButtonGroup);
const buttonGroup = shallow(<AnalyticsCollectionTable {...props} />).find(EuiButtonGroup);

expect(buttonGroup).toHaveLength(1);
expect(buttonGroup.prop('options')).toHaveLength(4);
expect(buttonGroup.prop('idSelected')).toEqual('Searches');
});

it('renders datePick', () => {
const datePicker = shallow(
<AnalyticsCollectionTable collections={analyticsCollections} />
).find(EuiSuperDatePicker);
const datePicker = shallow(<AnalyticsCollectionTable {...props} />).find(EuiSuperDatePicker);

expect(datePicker).toHaveLength(1);
expect(datePicker.prop('start')).toEqual('now-7d');
expect(datePicker.prop('end')).toEqual('now');
});

it('renders not found page', () => {
const wrapper = shallow(<AnalyticsCollectionTable {...props} collections={[]} />);

expect(wrapper.find(AnalyticsCollectionNotFound)).toHaveLength(1);
});
});
Loading

0 comments on commit 699a03a

Please sign in to comment.