[Enterprise Search] Fix documents pagination (#161830)

## Summary

This fixes broken pagination in access control indices, by splitting the
fetching logic out and making it unique per index name.
This commit is contained in:
Sander Philipse 2023-07-13 18:47:43 +08:00 committed by GitHub
parent e4ec664ae2
commit 1727a5ed77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 473 deletions

View file

@ -7,7 +7,7 @@
import type { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types';
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { HttpLogic } from '../../../shared/http';
export interface GetMappingsArgs {
@ -16,10 +16,15 @@ export interface GetMappingsArgs {
export type GetMappingsResponse = IndicesGetMappingIndexMappingRecord;
export type GetMappingsActions = Actions<GetMappingsArgs, GetMappingsResponse>;
export const getMappings = async ({ indexName }: GetMappingsArgs) => {
const route = `/internal/enterprise_search/mappings/${indexName}`;
return await HttpLogic.values.http.get<IndicesGetMappingIndexMappingRecord>(route);
};
export const mappingsWithPropsApiLogic = (indexName: string) =>
createApiLogic(['mappings_api_logic', indexName], getMappings);
export const MappingsApiLogic = createApiLogic(['mappings_api_logic'], getMappings);

View file

@ -9,20 +9,32 @@ import { SearchResponseBody } from '@elastic/elasticsearch/lib/api/types';
import { Meta } from '../../../../../common/types';
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { HttpLogic } from '../../../shared/http';
export interface SearchDocumentsApiLogicArgs {
docsPerPage?: number;
indexName: string;
pagination: { pageIndex: number; pageSize: number };
query?: string;
}
export interface SearchDocumentsApiLogicValues {
meta: Meta;
results: SearchResponseBody;
}
export type SearchDocumentsApiLogicActions = Actions<
SearchDocumentsApiLogicArgs,
SearchDocumentsApiLogicValues
>;
export const searchDocuments = async ({
docsPerPage,
indexName,
pagination,
query: searchQuery,
}: {
docsPerPage?: number;
indexName: string;
pagination: { pageIndex: number; pageSize: number };
query?: string;
}) => {
}: SearchDocumentsApiLogicArgs) => {
const newIndexName = encodeURIComponent(indexName);
const route = `/internal/enterprise_search/indices/${newIndexName}/search`;
const query = {
@ -30,7 +42,7 @@ export const searchDocuments = async ({
size: docsPerPage || pagination.pageSize,
};
return await HttpLogic.values.http.post<{ meta: Meta; results: SearchResponseBody }>(route, {
return await HttpLogic.values.http.post<SearchDocumentsApiLogicValues>(route, {
body: JSON.stringify({
searchQuery,
}),
@ -38,7 +50,5 @@ export const searchDocuments = async ({
});
};
export const SearchDocumentsApiLogic = createApiLogic(
['search_documents_api_logic'],
searchDocuments
);
export const searchDocumentsApiLogic = (indexName: string) =>
createApiLogic(['search_documents_api_logic', indexName], searchDocuments);

View file

@ -13,26 +13,22 @@ import { shallow } from 'enzyme';
import { EuiCallOut, EuiPagination } from '@elastic/eui';
import { Status } from '../../../../../../../common/types/api';
import { Result } from '../../../../../shared/result/result';
import { INDEX_DOCUMENTS_META_DEFAULT } from '../../documents_logic';
import { INDEX_DOCUMENTS_META_DEFAULT } from '../../documents';
import { DocumentList } from './document_list';
const mockActions = {};
export const DEFAULT_VALUES = {
data: undefined,
indexName: 'indexName',
docs: [],
docsPerPage: 25,
isLoading: true,
mappingData: undefined,
mappingStatus: 0,
mappings: undefined,
meta: INDEX_DOCUMENTS_META_DEFAULT,
query: '',
results: [],
status: Status.IDLE,
onPaginate: () => {},
setDocsPerPage: () => {},
};
const mockValues = { ...DEFAULT_VALUES };
@ -44,15 +40,15 @@ describe('DocumentList', () => {
setMockActions(mockActions);
});
it('renders empty', () => {
const wrapper = shallow(<DocumentList />);
const wrapper = shallow(<DocumentList {...DEFAULT_VALUES} />);
expect(wrapper.find(Result)).toHaveLength(0);
expect(wrapper.find(EuiPagination)).toHaveLength(2);
});
it('renders documents when results when there is data and mappings', () => {
setMockValues({
...mockValues,
results: [
const values = {
...DEFAULT_VALUES,
docs: [
{
_id: 'M9ntXoIBTq5dF-1Xnc8A',
_index: 'kibana_sample_data_flights',
@ -70,19 +66,20 @@ describe('DocumentList', () => {
},
},
],
simplifiedMapping: {
mappings: {
AvgTicketPrice: {
type: 'float',
type: 'float' as const,
},
},
});
};
const wrapper = shallow(<DocumentList />);
const wrapper = shallow(<DocumentList {...values} />);
expect(wrapper.find(Result)).toHaveLength(2);
});
it('renders callout when total results are 10.000', () => {
setMockValues({
const values = {
...DEFAULT_VALUES,
...mockValues,
meta: {
page: {
@ -90,8 +87,8 @@ describe('DocumentList', () => {
total_results: 10000,
},
},
});
const wrapper = shallow(<DocumentList />);
};
const wrapper = shallow(<DocumentList {...values} />);
expect(wrapper.find(EuiCallOut)).toHaveLength(1);
});
});

View file

@ -7,9 +7,9 @@
import React, { useState } from 'react';
import { useActions, useValues } from 'kea';
import { useValues } from 'kea';
import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
import { MappingProperty, SearchHit } from '@elastic/elasticsearch/lib/api/types';
import {
EuiButtonEmpty,
@ -29,22 +29,33 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedNumber } from '@kbn/i18n-react';
import { Meta } from '../../../../../../../common/types';
import { Result } from '../../../../../shared/result/result';
import { resultMetaData } from '../../../../../shared/result/result_metadata';
import { DocumentsLogic } from '../../documents_logic';
import { IndexViewLogic } from '../../index_view_logic';
export const DocumentList: React.FC = () => {
const {
docsPerPage,
isLoading,
meta,
results,
simplifiedMapping: mappings,
} = useValues(DocumentsLogic);
interface DocumentListProps {
docs: SearchHit[];
docsPerPage: number;
isLoading: boolean;
mappings: Record<string, MappingProperty> | undefined;
meta: Meta;
onPaginate: (newPageIndex: number) => void;
setDocsPerPage: (docsPerPage: number) => void;
}
export const DocumentList: React.FC<DocumentListProps> = ({
docs,
docsPerPage,
isLoading,
mappings,
meta,
onPaginate,
setDocsPerPage,
}) => {
const { ingestionMethod } = useValues(IndexViewLogic);
const { onPaginate, setDocsPerPage } = useActions(DocumentsLogic);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const resultToField = (result: SearchHit) => {
@ -88,7 +99,7 @@ export const DocumentList: React.FC = () => {
maximum: <FormattedNumber value={10000} />,
results: (
<strong>
<FormattedNumber value={results.length} />
<FormattedNumber value={docs.length} />
</strong>
),
total: (
@ -102,10 +113,10 @@ export const DocumentList: React.FC = () => {
</EuiText>
{isLoading && <EuiProgress size="xs" color="primary" />}
<EuiSpacer size="m" />
{results.map((result) => {
{docs.map((doc) => {
return (
<React.Fragment key={result._id}>
<Result fields={resultToField(result)} metaData={resultMetaData(result)} />
<React.Fragment key={doc._id}>
<Result fields={resultToField(doc)} metaData={resultMetaData(doc)} />
<EuiSpacer size="s" />
</React.Fragment>
);

View file

@ -20,25 +20,46 @@ import {
import { i18n } from '@kbn/i18n';
import { CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX } from '../../../../../common/constants';
import {
CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX,
ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT,
} from '../../../../../common/constants';
import { Status } from '../../../../../common/types/api';
import { DEFAULT_META } from '../../../shared/constants';
import { KibanaLogic } from '../../../shared/kibana';
import { mappingsWithPropsApiLogic } from '../../api/mappings/mappings_logic';
import { searchDocumentsApiLogic } from '../../api/search_documents/search_documents_api_logic';
import {
AccessControlIndexSelector,
AccessControlSelectorOption,
} from './components/access_control_index_selector/access_control_index_selector';
import { DocumentList } from './components/document_list/document_list';
import { DocumentsLogic, DEFAULT_PAGINATION } from './documents_logic';
import { IndexNameLogic } from './index_name_logic';
import { IndexViewLogic } from './index_view_logic';
import './documents.scss';
export const INDEX_DOCUMENTS_META_DEFAULT = {
page: {
current: 0,
size: ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT,
total_pages: 0,
total_results: 0,
},
};
export const DEFAULT_PAGINATION = {
pageIndex: INDEX_DOCUMENTS_META_DEFAULT.page.current,
pageSize: INDEX_DOCUMENTS_META_DEFAULT.page.size,
totalItemCount: INDEX_DOCUMENTS_META_DEFAULT.page.total_results,
};
export const SearchIndexDocuments: React.FC = () => {
const { indexName } = useValues(IndexNameLogic);
const { ingestionMethod, hasDocumentLevelSecurityFeature } = useValues(IndexViewLogic);
const { simplifiedMapping } = useValues(DocumentsLogic);
const { makeRequest, makeMappingRequest, setSearchQuery } = useActions(DocumentsLogic);
const { productFeatures } = useValues(KibanaLogic);
const [selectedIndexType, setSelectedIndexType] =
@ -47,18 +68,35 @@ export const SearchIndexDocuments: React.FC = () => {
selectedIndexType === 'content-index'
? indexName
: indexName.replace('search-', CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX);
const mappingLogic = mappingsWithPropsApiLogic(indexToShow);
const documentLogic = searchDocumentsApiLogic(indexToShow);
const { makeRequest: getDocuments } = useActions(documentLogic);
const { makeRequest: getMappings } = useActions(mappingLogic);
const { data, status } = useValues(documentLogic);
const { data: mappingData, status: mappingStatus } = useValues(mappingLogic);
const docs = data?.results?.hits.hits ?? [];
const [pagination, setPagination] = useState(DEFAULT_PAGINATION);
const [searchQuery, setSearchQuery] = useState('');
const shouldShowAccessControlSwitcher =
hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled;
useEffect(() => {
makeRequest({
getDocuments({
indexName: indexToShow,
pagination: DEFAULT_PAGINATION,
query: '',
pagination,
query: searchQuery,
});
makeMappingRequest({ indexName: indexToShow });
}, [indexToShow, indexName]);
}, [indexToShow, pagination, searchQuery]);
useEffect(() => {
setSearchQuery('');
setPagination(DEFAULT_PAGINATION);
getMappings({ indexName: indexToShow });
}, [indexToShow]);
return (
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="none">
@ -102,11 +140,21 @@ export const SearchIndexDocuments: React.FC = () => {
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
{!simplifiedMapping &&
{docs.length === 0 &&
i18n.translate('xpack.enterpriseSearch.content.searchIndex.documents.noMappings', {
defaultMessage: 'No documents found for index',
})}
{simplifiedMapping && <DocumentList />}
{docs.length > 0 && (
<DocumentList
docs={docs}
docsPerPage={pagination.pageSize}
isLoading={status !== Status.SUCCESS && mappingStatus !== Status.SUCCESS}
mappings={mappingData?.mappings?.properties ?? {}}
meta={data?.meta ?? DEFAULT_META}
onPaginate={(pageIndex) => setPagination({ ...pagination, pageIndex })}
setDocsPerPage={(pageSize) => setPagination({ ...pagination, pageSize })}
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>

View file

@ -1,243 +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 { nextTick } from '@kbn/test-jest-helpers';
import { HttpError, Status } from '../../../../../common/types/api';
import { MappingsApiLogic } from '../../api/mappings/mappings_logic';
import { SearchDocumentsApiLogic } from '../../api/search_documents/search_documents_api_logic';
import {
DocumentsLogic,
INDEX_DOCUMENTS_META_DEFAULT,
convertMetaToPagination,
} from './documents_logic';
import { IndexNameLogic } from './index_name_logic';
export const DEFAULT_VALUES = {
data: undefined,
docsPerPage: 25,
indexName: 'indexName',
isLoading: true,
mappingData: undefined,
mappingStatus: 0,
meta: INDEX_DOCUMENTS_META_DEFAULT,
query: '',
results: [],
status: Status.IDLE,
};
describe('DocumentsLogic', () => {
const { mount: mountIndexNameLogic } = new LogicMounter(IndexNameLogic);
const { mount: mountSearchDocumentsApiLogic } = new LogicMounter(SearchDocumentsApiLogic);
const { mount: mountMappingsApiLogic } = new LogicMounter(MappingsApiLogic);
const { mount } = new LogicMounter(DocumentsLogic);
beforeEach(() => {
jest.clearAllMocks();
const indexNameLogic = mountIndexNameLogic();
mountMappingsApiLogic();
mountSearchDocumentsApiLogic();
mount();
indexNameLogic.actions.setIndexName('indexName');
});
it('has expected default values', () => {
expect(DocumentsLogic.values).toEqual(DEFAULT_VALUES);
});
describe('actions', () => {
describe('setSearchQuery', () => {
it('sets query string', () => {
const newQueryString = 'foo';
expect(DocumentsLogic.values).toEqual({ ...DEFAULT_VALUES });
DocumentsLogic.actions.setSearchQuery(newQueryString);
expect(DocumentsLogic.values).toEqual({ ...DEFAULT_VALUES, query: newQueryString });
});
});
describe('setDocsPerPage', () => {
it('sets documents to show per page', () => {
const docsToShow = 50;
expect(DocumentsLogic.values).toEqual({ ...DEFAULT_VALUES });
DocumentsLogic.actions.setDocsPerPage(docsToShow);
expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
docsPerPage: docsToShow,
meta: {
page: {
...INDEX_DOCUMENTS_META_DEFAULT.page,
size: docsToShow,
},
},
simplifiedMapping: undefined,
status: Status.LOADING,
});
});
});
});
describe('listeners', () => {
describe('setSearchQuery', () => {
it('make documents apiRequest request after 250ms debounce', async () => {
jest.useFakeTimers({ legacyFakeTimers: true });
DocumentsLogic.actions.makeRequest = jest.fn();
DocumentsLogic.actions.setSearchQuery('test');
await nextTick();
expect(DocumentsLogic.actions.makeRequest).not.toHaveBeenCalled();
jest.advanceTimersByTime(250);
await nextTick();
expect(DocumentsLogic.actions.makeRequest).toHaveBeenCalledWith({
docsPerPage: 25,
indexName: 'indexName',
pagination: convertMetaToPagination(INDEX_DOCUMENTS_META_DEFAULT),
query: 'test',
});
jest.useRealTimers();
});
});
});
describe('selectors', () => {
describe('isLoading', () => {
it('sets isLoading false when mapping and documents requests are success', () => {
const mockSuccessData = {
_shards: { failed: 0, successful: 3, total: 3 },
hits: { hits: [] },
timed_out: false,
took: 3,
};
expect(DocumentsLogic.values).toEqual({ ...DEFAULT_VALUES });
MappingsApiLogic.actions.apiSuccess({ mappings: {} });
SearchDocumentsApiLogic.actions.apiSuccess({
meta: INDEX_DOCUMENTS_META_DEFAULT,
results: mockSuccessData,
});
expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
data: {
meta: INDEX_DOCUMENTS_META_DEFAULT,
results: mockSuccessData,
},
isLoading: false,
mappingData: {
mappings: {},
},
mappingStatus: Status.SUCCESS,
status: Status.SUCCESS,
});
});
it('sets isLoading false when one of mappings or documents requests are not done', () => {
expect(DocumentsLogic.values).toEqual({ ...DEFAULT_VALUES });
MappingsApiLogic.actions.apiError({} as HttpError);
expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
isLoading: true,
mappingStatus: Status.ERROR,
status: Status.IDLE,
});
});
});
describe('results', () => {
it('selects searchHits from the response body', () => {
const mockSuccessData = {
_shards: { failed: 0, successful: 3, total: 3 },
hits: { hits: [{ _id: '123', _index: 'indexName', searchHit: true }] },
timed_out: false,
took: 3,
};
MappingsApiLogic.actions.apiSuccess({ mappings: {} });
SearchDocumentsApiLogic.actions.apiSuccess({
meta: {
page: {
...INDEX_DOCUMENTS_META_DEFAULT.page,
total_pages: 1,
total_results: 1,
},
},
results: mockSuccessData,
});
expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
data: {
meta: {
page: {
...INDEX_DOCUMENTS_META_DEFAULT.page,
total_pages: 1,
total_results: 1,
},
},
results: mockSuccessData,
},
isLoading: false,
mappingData: {
mappings: {},
},
mappingStatus: Status.SUCCESS,
meta: {
page: {
...INDEX_DOCUMENTS_META_DEFAULT.page,
total_pages: 1,
total_results: 1,
},
},
results: [{ _id: '123', _index: 'indexName', searchHit: true }],
simplifiedMapping: undefined,
status: Status.SUCCESS,
});
});
it("returns empty when response doesn't have hits", () => {
const mockSuccessData = {
_shards: { failed: 0, successful: 3, total: 3 },
hits: { hits: [] },
timed_out: false,
took: 3,
};
MappingsApiLogic.actions.apiSuccess({ mappings: {} });
SearchDocumentsApiLogic.actions.apiSuccess({
meta: INDEX_DOCUMENTS_META_DEFAULT,
results: mockSuccessData,
});
expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
data: { meta: INDEX_DOCUMENTS_META_DEFAULT, results: mockSuccessData },
isLoading: false,
mappingData: {
mappings: {},
},
mappingStatus: Status.SUCCESS,
results: [],
status: Status.SUCCESS,
});
});
});
describe('simplifiedMapping', () => {
it('selects properties from the response body', () => {
MappingsApiLogic.actions.apiSuccess({
mappings: { properties: { some: { type: 'text' } } },
});
expect(DocumentsLogic.values).toEqual({
...DEFAULT_VALUES,
isLoading: true,
mappingData: {
mappings: { properties: { some: { type: 'text' } } },
},
mappingStatus: Status.SUCCESS,
simplifiedMapping: { some: { type: 'text' } },
});
});
});
});
});

View file

@ -1,165 +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 {
IndicesGetMappingIndexMappingRecord,
MappingProperty,
SearchResponseBody,
SearchHit,
} from '@elastic/elasticsearch/lib/api/types';
import { ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT } from '../../../../../common/constants';
import { Meta } from '../../../../../common/types';
import { Status } from '../../../../../common/types/api';
import { updateMetaPageIndex } from '../../../shared/table_pagination';
import { MappingsApiLogic } from '../../api/mappings/mappings_logic';
import { SearchDocumentsApiLogic } from '../../api/search_documents/search_documents_api_logic';
import { IndexNameLogic } from './index_name_logic';
export const INDEX_DOCUMENTS_META_DEFAULT = {
page: {
current: 0,
size: ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT,
total_pages: 0,
total_results: 0,
},
};
export const DEFAULT_PAGINATION = {
pageIndex: INDEX_DOCUMENTS_META_DEFAULT.page.current,
pageSize: INDEX_DOCUMENTS_META_DEFAULT.page.size,
totalItemCount: INDEX_DOCUMENTS_META_DEFAULT.page.total_results,
};
interface DocumentsLogicActions {
apiReset: typeof SearchDocumentsApiLogic.actions.apiReset;
makeMappingRequest: typeof MappingsApiLogic.actions.makeRequest;
makeRequest: typeof SearchDocumentsApiLogic.actions.makeRequest;
onPaginate(newPageIndex: number): { newPageIndex: number };
setDocsPerPage(docsPerPage: number): { docsPerPage: number };
setSearchQuery(query: string): { query: string };
}
export interface DocumentsLogicValues {
data: typeof SearchDocumentsApiLogic.values.data;
docsPerPage: number;
indexName: typeof IndexNameLogic.values.indexName;
isLoading: boolean;
mappingData: IndicesGetMappingIndexMappingRecord;
mappingStatus: Status;
meta: Meta;
query: string;
results: SearchHit[];
simplifiedMapping: Record<string, MappingProperty> | undefined;
status: Status;
}
export const convertMetaToPagination = (meta: Meta) => ({
pageIndex: meta.page.current,
pageSize: meta.page.size,
totalItemCount: meta.page.total_results,
});
export const DocumentsLogic = kea<MakeLogicType<DocumentsLogicValues, DocumentsLogicActions>>({
actions: {
onPaginate: (newPageIndex) => ({ newPageIndex }),
setDocsPerPage: (docsPerPage) => ({ docsPerPage }),
setSearchQuery: (query) => ({ query }),
},
connect: {
actions: [
SearchDocumentsApiLogic,
['apiReset', 'apiSuccess', 'makeRequest'],
MappingsApiLogic,
['makeRequest as makeMappingRequest'],
],
values: [
SearchDocumentsApiLogic,
['data', 'status'],
MappingsApiLogic,
['data as mappingData', 'status as mappingStatus'],
IndexNameLogic,
['indexName'],
],
},
listeners: ({ actions, values }) => ({
onPaginate: () => {
actions.makeRequest({
docsPerPage: values.docsPerPage,
indexName: values.indexName,
pagination: convertMetaToPagination(values.meta),
query: values.query,
});
},
setDocsPerPage: () => {
actions.makeRequest({
docsPerPage: values.docsPerPage,
indexName: values.indexName,
pagination: convertMetaToPagination(values.meta),
query: values.query,
});
},
setSearchQuery: async (_, breakpoint) => {
await breakpoint(250);
actions.makeRequest({
docsPerPage: values.docsPerPage,
indexName: values.indexName,
pagination: convertMetaToPagination(values.meta),
query: values.query,
});
},
}),
path: ['enterprise_search', 'search_index', 'documents'],
reducers: () => ({
docsPerPage: [
ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT,
{
setDocsPerPage: (_, { docsPerPage }) => docsPerPage,
},
],
meta: [
INDEX_DOCUMENTS_META_DEFAULT,
{
apiSuccess: (_, { meta }) => meta,
onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex),
setDocsPerPage: (_, { docsPerPage }) => ({
page: { ...INDEX_DOCUMENTS_META_DEFAULT.page, size: docsPerPage },
}),
},
],
query: [
'',
{
setSearchQuery: (_, { query }) => query,
},
],
}),
selectors: ({ selectors }) => ({
isLoading: [
() => [selectors.status, selectors.mappingStatus],
(status, mappingStatus) => status !== Status.SUCCESS || mappingStatus !== Status.SUCCESS,
],
results: [
() => [selectors.data],
(data: { results: SearchResponseBody }) => {
return data?.results?.hits?.hits || [];
},
],
simplifiedMapping: [
() => [selectors.mappingStatus, selectors.mappingData],
(status: Status, mapping: IndicesGetMappingIndexMappingRecord) => {
if (status !== Status.SUCCESS) return;
return mapping?.mappings?.properties;
},
],
}),
});

View file

@ -31,11 +31,12 @@ import { docLinks } from '../../../shared/doc_links';
import { KibanaLogic } from '../../../shared/kibana';
import { mappingsWithPropsApiLogic } from '../../api/mappings/mappings_logic';
import {
AccessControlIndexSelector,
AccessControlSelectorOption,
} from './components/access_control_index_selector/access_control_index_selector';
import { DocumentsLogic } from './documents_logic';
import { IndexNameLogic } from './index_name_logic';
import { IndexViewLogic } from './index_view_logic';
@ -43,8 +44,6 @@ import './index_mappings.scss';
export const SearchIndexIndexMappings: React.FC = () => {
const { indexName } = useValues(IndexNameLogic);
const { makeMappingRequest } = useActions(DocumentsLogic);
const { mappingData } = useValues(DocumentsLogic);
const { hasDocumentLevelSecurityFeature } = useValues(IndexViewLogic);
const { productFeatures } = useValues(KibanaLogic);
@ -54,7 +53,8 @@ export const SearchIndexIndexMappings: React.FC = () => {
selectedIndexType === 'content-index'
? indexName
: indexName.replace('search-', CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX);
const { makeRequest: makeMappingRequest } = useActions(mappingsWithPropsApiLogic(indexToShow));
const { data: mappingData } = useValues(mappingsWithPropsApiLogic(indexToShow));
const shouldShowAccessControlSwitch =
hasDocumentLevelSecurityFeature && productFeatures.hasDocumentLevelSecurityEnabled;
useEffect(() => {

View file

@ -26,6 +26,7 @@ export interface Actions<Args, Result> {
export interface CreateApiOptions<Result> {
clearFlashMessagesOnMakeRequest: boolean;
key: string; // for logics that use a key as a prop
requestBreakpointMS?: number;
showErrorFlash: boolean;
showSuccessFlashFn?: (result: Result) => string;

View file

@ -27,6 +27,7 @@ export const ResultFields: React.FC<Props> = ({ fields, isExpanded }) => {
fieldName={field.fieldName}
fieldValue={field.fieldValue}
fieldType={field.fieldType}
key={field.fieldName}
/>
))}
</EuiTableBody>