mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] Search indices list (#135135)
* Use real API call to fetch indices * Add Paths and pagination * Update table layout and i18n * Type changes and fixes * Update api logic with new utility function Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a852919b0d
commit
4908a3b5ed
22 changed files with 488 additions and 337 deletions
|
@ -26,7 +26,7 @@ Slicing up components into smaller chunks, designing clear interfaces for those
|
|||
|
||||
State management tools are most powerful when used to coordinate state across an entire application, or large slices of that application. To do that well, state needs to be shared and it needs to be clear where in the existing state to find what information. We do this by separating API data from component data.
|
||||
|
||||
This means API interactions and their data should live in their own logic files, and the resulting data and API status should be imported by other logic files or directly by components consuming that data. Those API logic files should contain all interactions with APIs, and the current status of those API requests. We have a util function to help you create those, located in [create_api_logic.ts](public/applications/shared/api_logic/create_api_logic.ts). You can grab the `status`, `data` and `error` values from any API created with that util. And you can listen to the `initiateCall`, `apiSuccess`, `apiError` and `apiReset` actions in other listeners.
|
||||
This means API interactions and their data should live in their own logic files, and the resulting data and API status should be imported by other logic files or directly by components consuming that data. Those API logic files should contain all interactions with APIs, and the current status of those API requests. We have a util function to help you create those, located in [create_api_logic.ts](public/applications/shared/api_logic/create_api_logic.ts). You can grab the `status`, `data` and `error` values from any API created with that util. And you can listen to the `makeRequest`, `apiSuccess`, `apiError` and `apiReset` actions in other listeners.
|
||||
|
||||
You will need to provide a function that actually makes the api call, as well as the logic path. The function will need to accept and return a single object, not separate values.
|
||||
|
||||
|
@ -73,7 +73,7 @@ export const AddCustomSourceLogic = kea<
|
|||
MakeLogicType<AddCustomSourceValues, AddCustomSourceActions, AddCustomSourceProps>
|
||||
>({
|
||||
connect: {
|
||||
actions: [AddCustomSourceApiLogic, ['initiateCall', 'apiSuccess', ]],
|
||||
actions: [AddCustomSourceApiLogic, ['makeRequest', 'apiSuccess', ]],
|
||||
values: [AddCustomSourceApiLogic, ['status']],
|
||||
},
|
||||
path: ['enterprise_search', 'workplace_search', 'add_custom_source_logic'],
|
||||
|
@ -85,7 +85,7 @@ export const AddCustomSourceLogic = kea<
|
|||
createContentSource: () => {
|
||||
const { customSourceNameValue } = values;
|
||||
const { baseServiceType } = props;
|
||||
actions.initiateCall({ source: customSourceNameValue, baseServiceType });
|
||||
actions.makeRequest({ source: customSourceNameValue, baseServiceType });
|
||||
},
|
||||
addSourceSuccess: (customSource: CustomSource) => {
|
||||
actions.setNewCustomSource(customSource);
|
||||
|
|
|
@ -5,11 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
HealthStatus,
|
||||
IndexName,
|
||||
IndicesStatsIndexMetadataState,
|
||||
Uuid,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
export interface ElasticsearchIndex {
|
||||
health?: string;
|
||||
status?: string;
|
||||
name: string;
|
||||
uuid?: string;
|
||||
health?: HealthStatus;
|
||||
status?: IndicesStatsIndexMetadataState;
|
||||
name: IndexName;
|
||||
uuid?: Uuid;
|
||||
total: {
|
||||
docs: {
|
||||
count: number;
|
||||
|
|
|
@ -9,6 +9,8 @@ import React, { useEffect } from 'react';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { HealthStatus } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import {
|
||||
EuiSelectable,
|
||||
EuiPanel,
|
||||
|
@ -25,10 +27,9 @@ import { EngineCreationLogic } from './engine_creation_logic';
|
|||
|
||||
import './search_index_selectable.scss';
|
||||
|
||||
export type HealthStrings = 'red' | 'green' | 'yellow' | 'unavailable';
|
||||
export interface SearchIndexSelectableOption {
|
||||
label: string;
|
||||
health: HealthStrings;
|
||||
health?: HealthStatus;
|
||||
status?: string;
|
||||
total: {
|
||||
docs: {
|
||||
|
@ -44,9 +45,11 @@ export interface SearchIndexSelectableOption {
|
|||
|
||||
const healthColorsMap = {
|
||||
red: 'danger',
|
||||
RED: 'danger',
|
||||
green: 'success',
|
||||
GREEN: 'success',
|
||||
yellow: 'warning',
|
||||
unavailable: '',
|
||||
YELLOW: 'warning',
|
||||
};
|
||||
|
||||
const renderIndexOption = (option: SearchIndexSelectableOption, searchValue: string) => {
|
||||
|
@ -57,7 +60,7 @@ const renderIndexOption = (option: SearchIndexSelectableOption, searchValue: str
|
|||
<EuiTextColor color="subdued">
|
||||
<small>
|
||||
<span className="selectableSecondaryContentLabel">
|
||||
<EuiIcon type="dot" color={healthColorsMap[option.health] ?? ''} />
|
||||
<EuiIcon type="dot" color={option.health ? healthColorsMap[option.health] : ''} />
|
||||
{option.health ?? '-'}
|
||||
</span>
|
||||
<span className="selectableSecondaryContentLabel" data-test-subj="optionStatus">
|
||||
|
|
|
@ -10,7 +10,7 @@ import { generatePath } from 'react-router-dom';
|
|||
import { ElasticsearchIndex } from '../../../../../common/types';
|
||||
import { ENGINE_CRAWLER_PATH, ENGINE_PATH } from '../../routes';
|
||||
|
||||
import { HealthStrings, SearchIndexSelectableOption } from './search_index_selectable';
|
||||
import { SearchIndexSelectableOption } from './search_index_selectable';
|
||||
|
||||
export const getRedirectToAfterEngineCreation = ({
|
||||
ingestionMethod,
|
||||
|
@ -38,7 +38,7 @@ export const formatIndicesToSelectable = (
|
|||
return indices.map((index) => ({
|
||||
...(selectedIndexName === index.name ? { checked: 'on' } : {}),
|
||||
label: index.name,
|
||||
health: (index.health as HealthStrings) ?? 'unavailable',
|
||||
health: index.health,
|
||||
status: index.status,
|
||||
total: index.total,
|
||||
}));
|
||||
|
|
|
@ -10,34 +10,34 @@ import { SearchIndex } from '../types';
|
|||
export const searchIndices = [
|
||||
{
|
||||
name: 'Our API Index',
|
||||
indexSlug: 'index-1',
|
||||
source_type: 'API',
|
||||
elasticsearch_index_name: 'ent-search-api-one',
|
||||
search_engines: 'Search Engine One, Search Engine Two',
|
||||
document_count: 100,
|
||||
health: 'green',
|
||||
data_ingestion: 'connected',
|
||||
storage: '9.3mb',
|
||||
},
|
||||
{
|
||||
name: 'Customer Feedback',
|
||||
indexSlug: 'index-2',
|
||||
source_type: 'Elasticsearch Index',
|
||||
elasticsearch_index_name: 'es-index-two',
|
||||
search_engines: 'Search Engine One',
|
||||
document_count: 100,
|
||||
health: 'green',
|
||||
data_ingestion: 'connected',
|
||||
storage: '9.3mb',
|
||||
},
|
||||
{
|
||||
name: 'Dharma Crawler',
|
||||
indexSlug: 'index-3',
|
||||
source_type: 'Crawler',
|
||||
elasticsearch_index_name: 'ent-search-crawler-one',
|
||||
search_engines: 'Search Engine One, Search Engine Two',
|
||||
document_count: 100,
|
||||
health: 'yellow',
|
||||
data_ingestion: 'incomplete',
|
||||
storage: '9.3mb',
|
||||
},
|
||||
{
|
||||
name: 'My Custom Source',
|
||||
indexSlug: 'index-4',
|
||||
source_type: 'Content Source',
|
||||
elasticsearch_index_name: 'ent-search-custom-source-one',
|
||||
search_engines: '--',
|
||||
document_count: 1,
|
||||
health: 'red',
|
||||
data_ingestion: 'incomplete',
|
||||
storage: '0mb',
|
||||
},
|
||||
] as SearchIndex[];
|
||||
|
|
|
@ -25,7 +25,6 @@ type MethodCrawlerActions = Pick<
|
|||
>;
|
||||
|
||||
export const MethodCrawlerLogic = kea<MakeLogicType<{}, MethodCrawlerActions>>({
|
||||
path: ['enterprise_search', 'method_crawler'],
|
||||
connect: {
|
||||
actions: [CreateCrawlerIndexApiLogic, ['apiError', 'apiSuccess', 'makeRequest']],
|
||||
},
|
||||
|
@ -34,8 +33,9 @@ export const MethodCrawlerLogic = kea<MakeLogicType<{}, MethodCrawlerActions>>({
|
|||
flashAPIErrors(error);
|
||||
},
|
||||
apiSuccess: ({ created }) => {
|
||||
KibanaLogic.values.navigateToUrl(SEARCH_INDEX_PATH.replace(':indexSlug', encodeURI(created)));
|
||||
KibanaLogic.values.navigateToUrl(SEARCH_INDEX_PATH.replace(':indexName', created));
|
||||
},
|
||||
makeRequest: () => clearFlashMessages(),
|
||||
},
|
||||
path: ['enterprise_search', 'method_crawler'],
|
||||
});
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
* Kibana intgegrations page
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { useActions } from 'kea';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
|
@ -29,7 +27,6 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
|
||||
import { baseBreadcrumbs } from '../search_indices';
|
||||
import { SearchIndicesLogic } from '../search_indices/search_indices_logic';
|
||||
|
||||
import { ButtonGroup, ButtonGroupOption } from './button_group';
|
||||
import { SearchIndexEmptyState } from './empty_state';
|
||||
|
@ -98,11 +95,6 @@ const METHOD_BUTTON_GROUP_OPTIONS: ButtonGroupOption[] = [
|
|||
export const NewIndex: React.FC = () => {
|
||||
const [selectedMethod, setSelectedMethod] = useState<ButtonGroupOption>();
|
||||
|
||||
const { loadSearchEngines } = useActions(SearchIndicesLogic);
|
||||
useEffect(() => {
|
||||
loadSearchEngines();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EnterpriseSearchContentPageTemplate
|
||||
pageChrome={[
|
||||
|
|
|
@ -12,22 +12,22 @@ import { formatApiName } from '../../utils/format_api_name';
|
|||
import { DEFAULT_LANGUAGE } from './constants';
|
||||
|
||||
export interface NewSearchIndexValues {
|
||||
rawName: string;
|
||||
name: string;
|
||||
language: string;
|
||||
name: string;
|
||||
rawName: string;
|
||||
}
|
||||
|
||||
export interface NewSearchIndexActions {
|
||||
setRawName(rawName: string): { rawName: string };
|
||||
setLanguage(language: string): { language: string };
|
||||
setRawName(rawName: string): { rawName: string };
|
||||
}
|
||||
|
||||
export const NewSearchIndexLogic = kea<MakeLogicType<NewSearchIndexValues, NewSearchIndexActions>>({
|
||||
path: ['enterprise_search', 'content', 'new_search_index'],
|
||||
actions: {
|
||||
setRawName: (rawName) => ({ rawName }),
|
||||
setLanguage: (language) => ({ language }),
|
||||
setRawName: (rawName) => ({ rawName }),
|
||||
},
|
||||
path: ['enterprise_search', 'content', 'new_search_index'],
|
||||
reducers: {
|
||||
language: [
|
||||
DEFAULT_LANGUAGE,
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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, mockFlashMessageHelpers } from '../../../__mocks__/kea_logic';
|
||||
import { searchIndices } from '../../__mocks__/search_indices.mock';
|
||||
|
||||
import { HttpError, Status } from '../../../../../common/types/api';
|
||||
|
||||
import { DEFAULT_META } from '../../../shared/constants';
|
||||
|
||||
import { IndicesAPILogic } from '../../logic/indices_api/indices_api_logic';
|
||||
|
||||
import { IndicesLogic } from './indices_logic';
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
data: undefined,
|
||||
indices: [],
|
||||
isLoading: false,
|
||||
meta: DEFAULT_META,
|
||||
status: Status.IDLE,
|
||||
};
|
||||
|
||||
describe('IndicesLogic', () => {
|
||||
const { mount: apiLogicMount } = new LogicMounter(IndicesAPILogic);
|
||||
const { mount } = new LogicMounter(IndicesLogic);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
apiLogicMount();
|
||||
mount();
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
describe('onPaginate', () => {
|
||||
it('updates meta with newPageIndex', () => {
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
IndicesLogic.actions.onPaginate(3);
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
meta: {
|
||||
page: {
|
||||
...DEFAULT_META.page,
|
||||
current: 3,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('reducers', () => {
|
||||
describe('meta', () => {
|
||||
it('updates when apiSuccess listener triggered', () => {
|
||||
const newMeta = {
|
||||
page: {
|
||||
current: 2,
|
||||
size: 5,
|
||||
total_pages: 10,
|
||||
total_results: 52,
|
||||
},
|
||||
};
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
IndicesLogic.actions.apiSuccess({ indices: searchIndices, meta: newMeta });
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
data: {
|
||||
indices: searchIndices,
|
||||
meta: newMeta,
|
||||
},
|
||||
indices: searchIndices,
|
||||
isLoading: false,
|
||||
meta: newMeta,
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('calls clearFlashMessages on new makeRequest', () => {
|
||||
IndicesLogic.actions.makeRequest({ meta: DEFAULT_META });
|
||||
expect(mockFlashMessageHelpers.clearFlashMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('calls flashAPIErrors on apiError', () => {
|
||||
IndicesLogic.actions.apiError({} as HttpError);
|
||||
expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledTimes(1);
|
||||
expect(mockFlashMessageHelpers.flashAPIErrors).toHaveBeenCalledWith({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectors', () => {
|
||||
describe('indices', () => {
|
||||
it('updates when apiSuccess listener triggered', () => {
|
||||
expect(IndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
IndicesLogic.actions.apiSuccess({ indices: searchIndices, meta: DEFAULT_META });
|
||||
|
||||
expect(IndicesLogic.values).toEqual({
|
||||
data: {
|
||||
indices: searchIndices,
|
||||
meta: DEFAULT_META,
|
||||
},
|
||||
indices: searchIndices,
|
||||
isLoading: false,
|
||||
meta: DEFAULT_META,
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { Meta } from '../../../../../common/types';
|
||||
import { HttpError, Status } from '../../../../../common/types/api';
|
||||
import { DEFAULT_META } from '../../../shared/constants';
|
||||
import { flashAPIErrors, clearFlashMessages } from '../../../shared/flash_messages';
|
||||
import { updateMetaPageIndex } from '../../../shared/table_pagination';
|
||||
import { IndicesAPILogic } from '../../logic/indices_api/indices_api_logic';
|
||||
import { SearchIndex } from '../../types';
|
||||
|
||||
export interface IndicesActions {
|
||||
apiError(error: HttpError): HttpError;
|
||||
apiSuccess({ indices, meta }: { indices: SearchIndex[]; meta: Meta }): {
|
||||
indices: SearchIndex[];
|
||||
meta: Meta;
|
||||
};
|
||||
makeRequest: typeof IndicesAPILogic.actions.makeRequest;
|
||||
onPaginate(newPageIndex: number): { newPageIndex: number };
|
||||
}
|
||||
export interface IndicesValues {
|
||||
data: typeof IndicesAPILogic.values.data;
|
||||
indices: SearchIndex[];
|
||||
isLoading: boolean;
|
||||
meta: Meta;
|
||||
status: typeof IndicesAPILogic.values.status;
|
||||
}
|
||||
|
||||
export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
|
||||
actions: { onPaginate: (newPageIndex) => ({ newPageIndex }) },
|
||||
connect: {
|
||||
actions: [IndicesAPILogic, ['makeRequest', 'apiSuccess', 'apiError']],
|
||||
values: [IndicesAPILogic, ['data', 'status']],
|
||||
},
|
||||
listeners: () => ({
|
||||
apiError: (e) => flashAPIErrors(e),
|
||||
makeRequest: () => clearFlashMessages(),
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'indices_logic'],
|
||||
reducers: () => ({
|
||||
meta: [
|
||||
DEFAULT_META,
|
||||
{
|
||||
apiSuccess: (_, { meta }) => meta,
|
||||
onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex),
|
||||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
indices: [() => [selectors.data], (data) => data?.indices || []],
|
||||
isLoading: [
|
||||
() => [selectors.status],
|
||||
(status) => {
|
||||
return status === Status.LOADING;
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -7,33 +7,41 @@
|
|||
|
||||
import '../../../__mocks__/shallow_useeffect.mock';
|
||||
import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic';
|
||||
import { searchIndices, searchEngines } from '../../__mocks__';
|
||||
import { searchIndices } from '../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiBasicTable } from '@elastic/eui';
|
||||
import { EuiBasicTable, EuiCallOut, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt';
|
||||
import { DEFAULT_META } from '../../../shared/constants';
|
||||
import { ElasticsearchResources } from '../../../shared/elasticsearch_resources';
|
||||
import { GettingStartedSteps } from '../../../shared/getting_started_steps';
|
||||
|
||||
import { SearchIndices } from './search_indices';
|
||||
|
||||
const mockValues = {
|
||||
indices: searchIndices,
|
||||
meta: DEFAULT_META,
|
||||
};
|
||||
|
||||
const mockActions = {
|
||||
initPage: jest.fn(),
|
||||
makeRequest: jest.fn(),
|
||||
onPaginate: jest.fn(),
|
||||
};
|
||||
|
||||
describe('SearchIndices', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
global.localStorage.clear();
|
||||
});
|
||||
describe('Empty state', () => {
|
||||
it('renders when both Search Indices and Search Engines empty', () => {
|
||||
it('renders when Indices are empty', () => {
|
||||
setMockValues({
|
||||
searchIndices: [],
|
||||
searchEngines: [],
|
||||
...mockValues,
|
||||
indices: [],
|
||||
});
|
||||
setMockActions(mockActions);
|
||||
const wrapper = shallow(<SearchIndices />);
|
||||
|
@ -44,43 +52,10 @@ describe('SearchIndices', () => {
|
|||
expect(wrapper.find(GettingStartedSteps)).toHaveLength(1);
|
||||
expect(wrapper.find(ElasticsearchResources)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders complete empty state when only Search Indices empty', () => {
|
||||
setMockValues({
|
||||
searchIndices: [],
|
||||
searchEngines,
|
||||
});
|
||||
setMockActions(mockActions);
|
||||
const wrapper = shallow(<SearchIndices />);
|
||||
|
||||
expect(wrapper.find(AddContentEmptyPrompt)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiBasicTable)).toHaveLength(0);
|
||||
|
||||
expect(wrapper.find(GettingStartedSteps)).toHaveLength(1);
|
||||
expect(wrapper.find(ElasticsearchResources)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders when only Search Engines empty', () => {
|
||||
setMockValues({
|
||||
searchIndices,
|
||||
searchEngines: [],
|
||||
});
|
||||
setMockActions(mockActions);
|
||||
const wrapper = shallow(<SearchIndices />);
|
||||
|
||||
expect(wrapper.find(AddContentEmptyPrompt)).toHaveLength(0);
|
||||
expect(wrapper.find(EuiBasicTable)).toHaveLength(1);
|
||||
|
||||
expect(wrapper.find(GettingStartedSteps)).toHaveLength(1);
|
||||
expect(wrapper.find(ElasticsearchResources)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders with Data', () => {
|
||||
setMockValues({
|
||||
searchIndices,
|
||||
searchEngines,
|
||||
});
|
||||
setMockValues(mockValues);
|
||||
setMockActions(mockActions);
|
||||
|
||||
const wrapper = shallow(<SearchIndices />);
|
||||
|
@ -91,6 +66,39 @@ describe('SearchIndices', () => {
|
|||
expect(wrapper.find(GettingStartedSteps)).toHaveLength(0);
|
||||
expect(wrapper.find(ElasticsearchResources)).toHaveLength(0);
|
||||
|
||||
expect(mockActions.initPage).toHaveBeenCalledTimes(1);
|
||||
expect(mockActions.makeRequest).toHaveBeenCalledTimes(1);
|
||||
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('dismisses callout on click to button', () => {
|
||||
setMockValues(mockValues);
|
||||
setMockActions(mockActions);
|
||||
|
||||
const wrapper = shallow(<SearchIndices />);
|
||||
const dismissButton = wrapper.find(EuiCallOut).find(EuiButton);
|
||||
expect(global.localStorage.getItem('enterprise-search-indices-callout-dismissed')).toBe(
|
||||
'false'
|
||||
);
|
||||
dismissButton.simulate('click');
|
||||
expect(global.localStorage.getItem('enterprise-search-indices-callout-dismissed')).toBe('true');
|
||||
});
|
||||
|
||||
it('sets table pagination correctly', () => {
|
||||
setMockValues(mockValues);
|
||||
setMockActions(mockActions);
|
||||
|
||||
const wrapper = shallow(<SearchIndices />);
|
||||
const table = wrapper.find(EuiBasicTable);
|
||||
|
||||
expect(table.prop('pagination')).toEqual({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
showPerPageOptions: false,
|
||||
totalItemCount: 0,
|
||||
});
|
||||
|
||||
table.simulate('change', { page: { index: 2 } });
|
||||
expect(mockActions.onPaginate).toHaveBeenCalledTimes(1);
|
||||
expect(mockActions.onPaginate).toHaveBeenCalledWith(3); // API's are 1 indexed, but table is 0 indexed
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,24 +14,36 @@ import { useValues, useActions } from 'kea';
|
|||
import {
|
||||
EuiBasicTable,
|
||||
EuiButton,
|
||||
EuiBadge,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
HorizontalAlignment,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt';
|
||||
import { ElasticsearchResources } from '../../../shared/elasticsearch_resources';
|
||||
import { GettingStartedSteps } from '../../../shared/getting_started_steps';
|
||||
import { EuiLinkTo, EuiButtonIconTo } from '../../../shared/react_router_helpers';
|
||||
|
||||
import { convertMetaToPagination, handlePageChange } from '../../../shared/table_pagination';
|
||||
import { useLocalStorage } from '../../../shared/use_local_storage';
|
||||
import { NEW_INDEX_PATH, SEARCH_INDEX_PATH } from '../../routes';
|
||||
import { SearchIndex } from '../../types';
|
||||
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
|
||||
|
||||
import { SearchIndicesLogic } from './search_indices_logic';
|
||||
import { IndicesLogic } from './indices_logic';
|
||||
|
||||
const healthColorsMap = {
|
||||
green: 'success',
|
||||
red: 'danger',
|
||||
unavailable: '',
|
||||
yellow: 'warning',
|
||||
};
|
||||
|
||||
export const baseBreadcrumbs = [
|
||||
i18n.translate('xpack.enterpriseSearch.content.searchIndices.content.breadcrumb', {
|
||||
|
@ -43,93 +55,99 @@ export const baseBreadcrumbs = [
|
|||
];
|
||||
|
||||
export const SearchIndices: React.FC = () => {
|
||||
const { initPage, searchEnginesLoadSuccess, searchIndicesLoadSuccess } =
|
||||
useActions(SearchIndicesLogic);
|
||||
const { searchIndices, searchEngines } = useValues(SearchIndicesLogic);
|
||||
const { makeRequest, onPaginate } = useActions(IndicesLogic);
|
||||
const { meta, indices, isLoading } = useValues(IndicesLogic);
|
||||
|
||||
const [calloutDismissed, setCalloutDismissed] = useLocalStorage<boolean>(
|
||||
'enterprise-search-indices-callout-dismissed',
|
||||
false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
initPage();
|
||||
}, []);
|
||||
makeRequest({ meta });
|
||||
}, [meta.page.current]);
|
||||
|
||||
// TODO This is for easy testing until we have the backend, please remove this before the release
|
||||
// @ts-ignore
|
||||
window.contentActions = {
|
||||
initPage,
|
||||
searchIndicesLoadSuccess,
|
||||
searchEnginesLoadSuccess,
|
||||
};
|
||||
|
||||
// TODO: Replace with a real list of indices
|
||||
const columns = [
|
||||
{
|
||||
field: 'name',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.name.columnTitle', {
|
||||
defaultMessage: 'Search index name',
|
||||
defaultMessage: 'Index name',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
render: (name: string, { indexSlug }: SearchIndex) => (
|
||||
render: (name: string) => (
|
||||
<EuiLinkTo
|
||||
data-test-subj="search-index-link"
|
||||
to={generatePath(SEARCH_INDEX_PATH, { indexSlug })}
|
||||
to={generatePath(SEARCH_INDEX_PATH, { indexName: name })}
|
||||
>
|
||||
{name}
|
||||
</EuiLinkTo>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'source_type',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.sourceType.columnTitle', {
|
||||
defaultMessage: 'Source type',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'elasticsearch_index_name',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.elasticsearchIndexName.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Elasticsearch index name',
|
||||
}
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'search_engines',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.searchEngines.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Attached search engines',
|
||||
}
|
||||
),
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'document_count',
|
||||
field: 'total.docs.count',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle', {
|
||||
defaultMessage: 'Documents',
|
||||
defaultMessage: 'Docs count',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
align: 'right' as HorizontalAlignment,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.actions.columnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
field: 'health',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.health.columnTitle', {
|
||||
defaultMessage: 'Index health',
|
||||
}),
|
||||
render: (health: 'red' | 'green' | 'yellow' | 'unavailable') => (
|
||||
<span>
|
||||
<EuiIcon type="dot" color={healthColorsMap[health] ?? ''} />
|
||||
{health ?? '-'}
|
||||
</span>
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'data_ingestion',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.dataIngestion.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Data ingestion',
|
||||
}
|
||||
),
|
||||
render: (dataIngestionStatus: string) =>
|
||||
dataIngestionStatus ? (
|
||||
<EuiBadge color={dataIngestionStatus === 'connected' ? 'success' : 'warning'}>
|
||||
{dataIngestionStatus}
|
||||
</EuiBadge>
|
||||
) : null,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
align: 'right' as HorizontalAlignment,
|
||||
field: 'total.store.size_in_bytes',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.storage.columnTitle', {
|
||||
defaultMessage: 'Storage',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
render: ({ indexSlug }: SearchIndex) => (
|
||||
render: ({ name }: SearchIndex) => (
|
||||
<EuiButtonIconTo
|
||||
iconType="eye"
|
||||
data-test-subj="view-search-index-button"
|
||||
to={generatePath(SEARCH_INDEX_PATH, { indexSlug })}
|
||||
to={generatePath(SEARCH_INDEX_PATH, {
|
||||
indexName: name,
|
||||
})}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.actions.columnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -155,7 +173,7 @@ export const SearchIndices: React.FC = () => {
|
|||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<GettingStartedSteps step={searchIndices.length === 0 ? 'first' : 'second'} />
|
||||
<GettingStartedSteps step={indices.length === 0 ? 'first' : 'second'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ElasticsearchResources />
|
||||
|
@ -165,7 +183,7 @@ export const SearchIndices: React.FC = () => {
|
|||
);
|
||||
|
||||
const pageTitle =
|
||||
searchIndices.length !== 0
|
||||
indices.length !== 0
|
||||
? i18n.translate('xpack.enterpriseSearch.content.searchIndices.searchIndices.pageTitle', {
|
||||
defaultMessage: 'Content',
|
||||
})
|
||||
|
@ -187,7 +205,7 @@ export const SearchIndices: React.FC = () => {
|
|||
rightSideItems: [createNewIndexButton],
|
||||
}}
|
||||
>
|
||||
{searchIndices.length !== 0 ? (
|
||||
{indices.length !== 0 || isLoading ? (
|
||||
<>
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
|
@ -200,13 +218,54 @@ export const SearchIndices: React.FC = () => {
|
|||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBasicTable items={searchIndices} columns={columns} />
|
||||
{!calloutDismissed && (
|
||||
<EuiCallOut
|
||||
size="m"
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.callout.title', {
|
||||
defaultMessage: 'Introducing Elasticsearch indices in Enterprise Search',
|
||||
})}
|
||||
iconType="iInCircle"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.indices.callout.text"
|
||||
defaultMessage="Your Elasticsearch indices are now front and center in Enterprise Search. You can create new indices and build search experiences with them directly. To learn more about how to use Elasticsearch indices in Enterprise Search {docLink}"
|
||||
values={{
|
||||
docLink: (
|
||||
<EuiLinkTo data-test-subj="search-index-link" to="#">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.callout.docLink',
|
||||
{
|
||||
defaultMessage: 'read the documentation',
|
||||
}
|
||||
)}
|
||||
</EuiLinkTo>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiButton fill onClick={() => setCalloutDismissed(true)}>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.callout.dismissButton', {
|
||||
defaultMessage: 'Dismiss',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBasicTable
|
||||
items={indices}
|
||||
columns={columns}
|
||||
onChange={handlePageChange(onPaginate)}
|
||||
pagination={{ ...convertMetaToPagination(meta), showPerPageOptions: false }}
|
||||
tableLayout="auto"
|
||||
loading={isLoading}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<AddContentEmptyPrompt />
|
||||
)}
|
||||
<EuiSpacer size="xxl" />
|
||||
{(searchEngines.length === 0 || searchIndices.length === 0) && engineSteps}
|
||||
{indices.length === 0 && !isLoading && engineSteps}
|
||||
</EnterpriseSearchContentPageTemplate>
|
||||
)
|
||||
</>
|
||||
|
|
|
@ -1,55 +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 { searchIndices, searchEngines } from '../../__mocks__';
|
||||
|
||||
import { SearchIndicesLogic } from './search_indices_logic';
|
||||
|
||||
describe('SearchIndicesLogic', () => {
|
||||
const { mount } = new LogicMounter(SearchIndicesLogic);
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
searchEngines: [],
|
||||
searchIndices: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mount();
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
expect(SearchIndicesLogic.values).toEqual(DEFAULT_VALUES);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
describe('searchIndicesLoadSuccess', () => {
|
||||
it('should set searchIndices', () => {
|
||||
SearchIndicesLogic.actions.searchIndicesLoadSuccess(searchIndices);
|
||||
expect(SearchIndicesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
searchIndices,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('searchEnginesLoadSuccess', () => {
|
||||
it('should set searchEngines', () => {
|
||||
SearchIndicesLogic.actions.searchEnginesLoadSuccess(searchEngines);
|
||||
expect(SearchIndicesLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
searchEngines,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('listeners', () => {
|
||||
describe('loadSearchEngines', () => {});
|
||||
describe('loadSearchIndices', () => {});
|
||||
});
|
||||
});
|
|
@ -1,79 +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 {
|
||||
searchIndices as searchIndicesMock,
|
||||
searchEngines as searchEnginesMock,
|
||||
} from '../../__mocks__';
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { Engine } from '../../../app_search/components/engine/types';
|
||||
import { flashAPIErrors } from '../../../shared/flash_messages';
|
||||
import { SearchIndex } from '../../types';
|
||||
|
||||
export interface SearchIndicesValues {
|
||||
searchIndices: SearchIndex[];
|
||||
searchEngines: Engine[];
|
||||
}
|
||||
|
||||
export interface SearchIndicesActions {
|
||||
initPage(): void;
|
||||
loadSearchEngines(): void;
|
||||
searchEnginesLoadSuccess(searchEngines: Engine[]): Engine[]; // TODO proper types when backend ready
|
||||
loadSearchIndices(): void;
|
||||
searchIndicesLoadSuccess(searchIndices: SearchIndex[]): SearchIndex[]; // TODO proper types when backend ready
|
||||
}
|
||||
|
||||
export const SearchIndicesLogic = kea<MakeLogicType<SearchIndicesValues, SearchIndicesActions>>({
|
||||
path: ['enterprise_search', 'content', 'search_indices'],
|
||||
actions: {
|
||||
initPage: true,
|
||||
loadSearchIndices: true,
|
||||
searchIndicesLoadSuccess: (searchIndices) => searchIndices,
|
||||
loadSearchEngines: true,
|
||||
searchEnginesLoadSuccess: (searchEngines) => searchEngines,
|
||||
},
|
||||
reducers: {
|
||||
searchIndices: [
|
||||
[],
|
||||
{
|
||||
searchIndicesLoadSuccess: (_, searchIndices) => searchIndices,
|
||||
},
|
||||
],
|
||||
searchEngines: [
|
||||
[],
|
||||
{
|
||||
searchEnginesLoadSuccess: (_, searchEngines) => searchEngines,
|
||||
},
|
||||
],
|
||||
},
|
||||
listeners: ({ actions }) => ({
|
||||
initPage: async () => {
|
||||
actions.loadSearchEngines();
|
||||
actions.loadSearchIndices();
|
||||
},
|
||||
loadSearchEngines: async () => {
|
||||
try {
|
||||
// TODO replace with actual backend call, add test cases
|
||||
const response = await Promise.resolve(searchEnginesMock);
|
||||
actions.searchEnginesLoadSuccess(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
loadSearchIndices: async () => {
|
||||
try {
|
||||
// TODO replace with actual backend call, add test cases
|
||||
const response = await Promise.resolve(searchIndicesMock);
|
||||
actions.searchIndicesLoadSuccess(response);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Meta } from '../../../../../common/types';
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { SearchIndex } from '../../types';
|
||||
|
||||
export const indicesApi = async ({ meta }: { meta: Meta }) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const route = '/internal/enterprise_search/indices';
|
||||
const query = {
|
||||
page: meta.page.current,
|
||||
size: meta.page.size,
|
||||
};
|
||||
const response = await http.get<{ indices: SearchIndex[]; meta: Meta }>(route, {
|
||||
query,
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export const IndicesAPILogic = createApiLogic(['content', 'indices_api_logic'], indicesApi);
|
|
@ -9,11 +9,14 @@
|
|||
* As of 2022-04-04, this shape is still in debate. Specifically, the `source_type` will be changing as we get closer to 8.3.
|
||||
* These merely serve as placeholders for static data for now.
|
||||
*/
|
||||
|
||||
import { HealthStatus } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
export interface SearchIndex {
|
||||
name: string;
|
||||
indexSlug: string;
|
||||
source_type: string;
|
||||
elasticsearch_index_name: string;
|
||||
search_engines: string;
|
||||
document_count: number;
|
||||
health: HealthStatus;
|
||||
data_ingestion: 'connected' | 'incomplete';
|
||||
storage: string;
|
||||
}
|
||||
|
|
|
@ -42,10 +42,19 @@ describe('CreateApiLogic', () => {
|
|||
logic.actions.makeRequest({});
|
||||
expect(logic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
apiStatus: { status: Status.LOADING },
|
||||
status: Status.LOADING,
|
||||
});
|
||||
});
|
||||
|
||||
it('should set persist data in between new requests', () => {
|
||||
logic.actions.apiSuccess(123);
|
||||
logic.actions.makeRequest({});
|
||||
expect(logic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
apiStatus: { data: 123, status: Status.LOADING },
|
||||
data: 123,
|
||||
status: Status.LOADING,
|
||||
apiStatus: {
|
||||
status: Status.LOADING,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -54,12 +63,12 @@ describe('CreateApiLogic', () => {
|
|||
logic.actions.apiSuccess({ success: 'data' });
|
||||
expect(logic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
status: Status.SUCCESS,
|
||||
data: { success: 'data' },
|
||||
apiStatus: {
|
||||
status: Status.SUCCESS,
|
||||
data: { success: 'data' },
|
||||
status: Status.SUCCESS,
|
||||
},
|
||||
data: { success: 'data' },
|
||||
status: Status.SUCCESS,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -68,14 +77,14 @@ describe('CreateApiLogic', () => {
|
|||
logic.actions.apiError('error' as any as HttpError);
|
||||
expect(logic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
status: Status.ERROR,
|
||||
data: undefined,
|
||||
error: 'error',
|
||||
apiStatus: {
|
||||
status: Status.ERROR,
|
||||
data: undefined,
|
||||
error: 'error',
|
||||
status: Status.ERROR,
|
||||
},
|
||||
data: undefined,
|
||||
error: 'error',
|
||||
status: Status.ERROR,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -84,14 +93,14 @@ describe('CreateApiLogic', () => {
|
|||
logic.actions.apiError('error' as any as HttpError);
|
||||
expect(logic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
status: Status.ERROR,
|
||||
data: undefined,
|
||||
error: 'error',
|
||||
apiStatus: {
|
||||
status: Status.ERROR,
|
||||
data: undefined,
|
||||
error: 'error',
|
||||
status: Status.ERROR,
|
||||
},
|
||||
data: undefined,
|
||||
error: 'error',
|
||||
status: Status.ERROR,
|
||||
});
|
||||
logic.actions.apiReset();
|
||||
expect(logic.values).toEqual(DEFAULT_VALUES);
|
||||
|
@ -115,14 +124,19 @@ describe('CreateApiLogic', () => {
|
|||
const apiSuccessMock = jest.spyOn(logic.actions, 'apiSuccess');
|
||||
const apiErrorMock = jest.spyOn(logic.actions, 'apiError');
|
||||
apiCallMock.mockReturnValue(
|
||||
Promise.reject({ body: { statusCode: 404, message: 'message' } })
|
||||
Promise.reject({
|
||||
body: {
|
||||
message: 'message',
|
||||
statusCode: 404,
|
||||
},
|
||||
})
|
||||
);
|
||||
logic.actions.makeRequest({ arg: 'argument1' });
|
||||
expect(apiCallMock).toHaveBeenCalledWith({ arg: 'argument1' });
|
||||
await nextTick();
|
||||
expect(apiSuccessMock).not.toHaveBeenCalled();
|
||||
expect(apiErrorMock).toHaveBeenCalledWith({
|
||||
body: { statusCode: 404, message: 'message' },
|
||||
body: { message: 'message', statusCode: 404 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,16 +11,16 @@ import { ApiStatus, Status, HttpError } from '../../../../common/types/api';
|
|||
|
||||
export interface Values<T> {
|
||||
apiStatus: ApiStatus<T>;
|
||||
status: Status;
|
||||
data?: T;
|
||||
error: HttpError;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
export interface Actions<Args, Result> {
|
||||
makeRequest(args: Args): Args;
|
||||
apiError(error: HttpError): HttpError;
|
||||
apiSuccess(result: Result): Result;
|
||||
apiReset(): void;
|
||||
apiSuccess(result: Result): Result;
|
||||
makeRequest(args: Args): Args;
|
||||
}
|
||||
|
||||
export const createApiLogic = <Result, Args>(
|
||||
|
@ -28,36 +28,12 @@ export const createApiLogic = <Result, Args>(
|
|||
apiFunction: (args: Args) => Promise<Result>
|
||||
) =>
|
||||
kea<MakeLogicType<Values<Result>, Actions<Args, Result>>>({
|
||||
path: ['enterprise_search', ...path],
|
||||
actions: {
|
||||
makeRequest: (args) => args,
|
||||
apiError: (error) => error,
|
||||
apiSuccess: (result) => result,
|
||||
apiReset: true,
|
||||
apiSuccess: (result) => result,
|
||||
makeRequest: (args) => args,
|
||||
},
|
||||
reducers: () => ({
|
||||
apiStatus: [
|
||||
{
|
||||
status: Status.IDLE,
|
||||
},
|
||||
{
|
||||
makeRequest: () => ({
|
||||
status: Status.LOADING,
|
||||
}),
|
||||
apiError: (_, error) => ({
|
||||
status: Status.ERROR,
|
||||
error,
|
||||
}),
|
||||
apiSuccess: (_, data) => ({
|
||||
status: Status.SUCCESS,
|
||||
data,
|
||||
}),
|
||||
apiReset: () => ({
|
||||
status: Status.IDLE,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions }) => ({
|
||||
makeRequest: async (args) => {
|
||||
try {
|
||||
|
@ -68,9 +44,34 @@ export const createApiLogic = <Result, Args>(
|
|||
}
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', ...path],
|
||||
reducers: () => ({
|
||||
apiStatus: [
|
||||
{
|
||||
status: Status.IDLE,
|
||||
},
|
||||
{
|
||||
apiError: (_, error) => ({
|
||||
error,
|
||||
status: Status.ERROR,
|
||||
}),
|
||||
apiReset: () => ({ status: Status.IDLE }),
|
||||
apiSuccess: (_, data) => ({
|
||||
data,
|
||||
status: Status.SUCCESS,
|
||||
}),
|
||||
makeRequest: ({ data }) => {
|
||||
return {
|
||||
data,
|
||||
status: Status.LOADING,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
status: [() => [selectors.apiStatus], (apiStatus: ApiStatus<Result>) => apiStatus.status],
|
||||
data: [() => [selectors.apiStatus], (apiStatus: ApiStatus<Result>) => apiStatus.data],
|
||||
error: [() => [selectors.apiStatus], (apiStatus: ApiStatus<Result>) => apiStatus.error],
|
||||
status: [() => [selectors.apiStatus], (apiStatus: ApiStatus<Result>) => apiStatus.status],
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -36,7 +36,7 @@ export function registerIndexRoutes({ router }: RouteDependencies) {
|
|||
path: '/internal/enterprise_search/indices',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
page: schema.number({ defaultValue: 1, min: 0 }),
|
||||
page: schema.number({ defaultValue: 0, min: 0 }),
|
||||
size: schema.number({ defaultValue: 10, min: 0 }),
|
||||
}),
|
||||
},
|
||||
|
@ -57,8 +57,8 @@ export function registerIndexRoutes({ router }: RouteDependencies) {
|
|||
page: {
|
||||
current: page,
|
||||
size,
|
||||
totalPages,
|
||||
totalResults,
|
||||
total_pages: totalPages,
|
||||
total_results: totalResults,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -11702,15 +11702,12 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.content.breadcrumb": "Contenu",
|
||||
"xpack.enterpriseSearch.content.searchIndices.create.buttonTitle": "Créer un nouvel index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle": "Documents",
|
||||
"xpack.enterpriseSearch.content.searchIndices.elasticsearchIndexName.columnTitle": "Nom de l'index Elasticsearch",
|
||||
"xpack.enterpriseSearch.content.searchIndices.name.columnTitle": "Nom de l'index de recherche",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchEngines.columnTitle": "Moteurs de recherche attachés",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.breadcrumb": "Rechercher dans les index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.emptyPageTitle": "Bienvenue dans Enterprise Search",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.pageTitle": "Contenu",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle": "Créer de belles expériences de recherche avec Enterprise Search",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.tableTitle": "Rechercher dans les index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.sourceType.columnTitle": "Type de source",
|
||||
"xpack.enterpriseSearch.curations.settings.licenseUpgradeLink": "En savoir plus sur les mises à niveau incluses dans la licence",
|
||||
"xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel": "Démarrer un essai gratuit de 30 jours",
|
||||
"xpack.enterpriseSearch.elasticsearch.nav.contentTitle": "Elasticsearch",
|
||||
|
|
|
@ -11693,15 +11693,12 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.content.breadcrumb": "コンテンツ",
|
||||
"xpack.enterpriseSearch.content.searchIndices.create.buttonTitle": "新しいインデックスを作成",
|
||||
"xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle": "ドキュメント",
|
||||
"xpack.enterpriseSearch.content.searchIndices.elasticsearchIndexName.columnTitle": "Elasticsearchインデックス名",
|
||||
"xpack.enterpriseSearch.content.searchIndices.name.columnTitle": "検索インデックス名",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchEngines.columnTitle": "接続された検索エンジン",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.breadcrumb": "インデックスの検索",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.emptyPageTitle": "エンタープライズ サーチへようこそ",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.pageTitle": "コンテンツ",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle": "エンタープライズ サーチで構築する優れた検索エクスペリエンス",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.tableTitle": "インデックスの検索",
|
||||
"xpack.enterpriseSearch.content.searchIndices.sourceType.columnTitle": "ソースタイプ",
|
||||
"xpack.enterpriseSearch.curations.settings.licenseUpgradeLink": "ライセンスアップグレードの詳細",
|
||||
"xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel": "30 日間のトライアルの開始",
|
||||
"xpack.enterpriseSearch.elasticsearch.nav.contentTitle": "Elasticsearch",
|
||||
|
|
|
@ -11708,15 +11708,12 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.content.breadcrumb": "内容",
|
||||
"xpack.enterpriseSearch.content.searchIndices.create.buttonTitle": "创建新索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle": "文档",
|
||||
"xpack.enterpriseSearch.content.searchIndices.elasticsearchIndexName.columnTitle": "Elasticsearch 索引名称",
|
||||
"xpack.enterpriseSearch.content.searchIndices.name.columnTitle": "搜索索引名称",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchEngines.columnTitle": "已附加搜索引擎",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.breadcrumb": "搜索索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.emptyPageTitle": "欢迎使用 Enterprise Search",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.pageTitle": "内容",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle": "通过 Enterprise Search 构建出色的搜索体验",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.tableTitle": "搜索索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.sourceType.columnTitle": "源类型",
|
||||
"xpack.enterpriseSearch.curations.settings.licenseUpgradeLink": "详细了解许可证升级",
|
||||
"xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel": "开始为期 30 天的试用",
|
||||
"xpack.enterpriseSearch.elasticsearch.nav.contentTitle": "Elasticsearch",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue