mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Update Search Index Document Card design. (#194061)
## Summary https://github.com/user-attachments/assets/552e1198-ba95-45d6-b13b-e1b26060d34c This PR adds an option to add a richer design for Result card. Defaults still stay the same with old design. Screenshot from Index Management. <img width="1139" alt="Screenshot 2024-09-27 at 14 52 25" src="https://github.com/user-attachments/assets/754a22c5-c3db-4385-b1ad-4e93a8615b9c"> Added a bunch of options to show score, show amount of fields to show when collapsed by default etc. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e373e44377
commit
5bc33cd69e
20 changed files with 614 additions and 250 deletions
|
@ -9,7 +9,7 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { MappingProperty, SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { IndicesGetMappingResponse, SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
|
@ -30,18 +30,22 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { FormattedMessage, FormattedNumber } from '@kbn/i18n-react';
|
||||
|
||||
import { resultMetaData, resultToField } from './result/result_metadata';
|
||||
import { resultMetaData, resultToFieldFromMappingResponse } from './result/result_metadata';
|
||||
|
||||
import { Result } from '..';
|
||||
import { type ResultProps } from './result/result';
|
||||
|
||||
interface DocumentListProps {
|
||||
dataTelemetryIdPrefix: string;
|
||||
docs: SearchHit[];
|
||||
docsPerPage: number;
|
||||
isLoading: boolean;
|
||||
mappings: Record<string, MappingProperty> | undefined;
|
||||
mappings: IndicesGetMappingResponse | undefined;
|
||||
meta: Pagination;
|
||||
onPaginate: (newPageIndex: number) => void;
|
||||
setDocsPerPage: (docsPerPage: number) => void;
|
||||
setDocsPerPage?: (docsPerPage: number) => void;
|
||||
onDocumentClick?: (doc: SearchHit) => void;
|
||||
resultProps?: Partial<ResultProps>;
|
||||
}
|
||||
|
||||
export const DocumentList: React.FC<DocumentListProps> = ({
|
||||
|
@ -53,6 +57,8 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||
meta,
|
||||
onPaginate,
|
||||
setDocsPerPage,
|
||||
onDocumentClick,
|
||||
resultProps = {},
|
||||
}) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
|
@ -99,7 +105,12 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||
{docs.map((doc) => {
|
||||
return (
|
||||
<React.Fragment key={doc._id}>
|
||||
<Result fields={resultToField(doc, mappings)} metaData={resultMetaData(doc)} />
|
||||
<Result
|
||||
fields={resultToFieldFromMappingResponse(doc, mappings)}
|
||||
metaData={resultMetaData(doc)}
|
||||
onDocumentClick={onDocumentClick ? () => onDocumentClick(doc) : undefined}
|
||||
{...resultProps}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -116,81 +127,83 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||
onPageClick={onPaginate}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
aria-label={i18n.translate('searchIndexDocuments.documentList.docsPerPage', {
|
||||
defaultMessage: 'Document count per page dropdown',
|
||||
})}
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id={`${dataTelemetryIdPrefix}-documents-docsPerPage`}
|
||||
{setDocsPerPage && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
aria-label={i18n.translate('searchIndexDocuments.documentList.docsPerPage', {
|
||||
defaultMessage: 'Document count per page dropdown',
|
||||
})}
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id={`${dataTelemetryIdPrefix}-documents-docsPerPage`}
|
||||
size="s"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(true);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.pagination.itemsPerPage', {
|
||||
defaultMessage: 'Documents per page: {docPerPage}',
|
||||
values: { docPerPage: docsPerPage },
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => {
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
size="s"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(true);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.pagination.itemsPerPage', {
|
||||
defaultMessage: 'Documents per page: {docPerPage}',
|
||||
values: { docPerPage: docsPerPage },
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => {
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
size="s"
|
||||
items={[
|
||||
<EuiContextMenuItem
|
||||
key="10 rows"
|
||||
icon={getIconType(10)}
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
setDocsPerPage(10);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
|
||||
defaultMessage: '{docCount} documents',
|
||||
values: { docCount: 10 },
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
items={[
|
||||
<EuiContextMenuItem
|
||||
key="10 rows"
|
||||
icon={getIconType(10)}
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
setDocsPerPage(10);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
|
||||
defaultMessage: '{docCount} documents',
|
||||
values: { docCount: 10 },
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
|
||||
<EuiContextMenuItem
|
||||
key="25 rows"
|
||||
icon={getIconType(25)}
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
setDocsPerPage(25);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
|
||||
defaultMessage: '{docCount} documents',
|
||||
values: { docCount: 25 },
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="50 rows"
|
||||
icon={getIconType(50)}
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
setDocsPerPage(50);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
|
||||
defaultMessage: '{docCount} documents',
|
||||
values: { docCount: 50 },
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
<EuiContextMenuItem
|
||||
key="25 rows"
|
||||
icon={getIconType(25)}
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
setDocsPerPage(25);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
|
||||
defaultMessage: '{docCount} documents',
|
||||
values: { docCount: 25 },
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="50 rows"
|
||||
icon={getIconType(50)}
|
||||
onClick={() => {
|
||||
setIsPopoverOpen(false);
|
||||
setDocsPerPage(50);
|
||||
}}
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
|
||||
defaultMessage: '{docCount} documents',
|
||||
values: { docCount: 50 },
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
|
|
@ -58,8 +58,14 @@ describe('DocumentList', () => {
|
|||
},
|
||||
],
|
||||
mappings: {
|
||||
AvgTicketPrice: {
|
||||
type: 'float' as const,
|
||||
kibana_sample_data_flights: {
|
||||
mappings: {
|
||||
properties: {
|
||||
AvgTicketPrice: {
|
||||
type: 'float' as const,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,4 +8,8 @@
|
|||
*/
|
||||
|
||||
export { Result } from './result';
|
||||
export { resultMetaData, resultToField } from './result_metadata';
|
||||
export {
|
||||
resultMetaData,
|
||||
resultToFieldFromMappingResponse,
|
||||
resultToFieldFromMappings as resultToField,
|
||||
} from './result_metadata';
|
||||
|
|
|
@ -9,76 +9,147 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ResultFields } from './results_fields';
|
||||
|
||||
import { ResultHeader } from './result_header';
|
||||
import './result.scss';
|
||||
import { MetaDataProps, ResultFieldProps } from './result_types';
|
||||
import { RichResultHeader } from './rich_result_header';
|
||||
import { ResultHeader } from './result_header';
|
||||
|
||||
interface ResultProps {
|
||||
export const DEFAULT_VISIBLE_FIELDS = 3;
|
||||
|
||||
export interface ResultProps {
|
||||
fields: ResultFieldProps[];
|
||||
metaData: MetaDataProps;
|
||||
defaultVisibleFields?: number;
|
||||
showScore?: boolean;
|
||||
compactCard?: boolean;
|
||||
onDocumentClick?: () => void;
|
||||
}
|
||||
|
||||
export const Result: React.FC<ResultProps> = ({ metaData, fields }) => {
|
||||
export const Result: React.FC<ResultProps> = ({
|
||||
metaData,
|
||||
fields,
|
||||
defaultVisibleFields = DEFAULT_VISIBLE_FIELDS,
|
||||
compactCard = true,
|
||||
showScore = false,
|
||||
onDocumentClick,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const tooltipText =
|
||||
fields.length <= 3
|
||||
fields.length <= defaultVisibleFields
|
||||
? i18n.translate('searchIndexDocuments.result.expandTooltip.allVisible', {
|
||||
defaultMessage: 'All fields are visible',
|
||||
})
|
||||
: isExpanded
|
||||
? i18n.translate('searchIndexDocuments.result.expandTooltip.showFewer', {
|
||||
defaultMessage: 'Show {amount} fewer fields',
|
||||
values: { amount: fields.length - 3 },
|
||||
values: { amount: fields.length - defaultVisibleFields },
|
||||
})
|
||||
: i18n.translate('searchIndexDocuments.result.expandTooltip.showMore', {
|
||||
defaultMessage: 'Show {amount} more fields',
|
||||
values: { amount: fields.length - 3 },
|
||||
values: { amount: fields.length - defaultVisibleFields },
|
||||
});
|
||||
const toolTipContent = <>{tooltipText}</>;
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder paddingSize="s" data-test-subj="search-index-documents-result">
|
||||
<EuiPanel
|
||||
hasBorder
|
||||
data-test-subj="search-index-documents-result"
|
||||
paddingSize={compactCard ? 's' : 'l'}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
justifyContent="spaceAround"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ResultHeader
|
||||
title={
|
||||
metaData.title ??
|
||||
i18n.translate('searchIndexDocuments.result.title.id', {
|
||||
defaultMessage: 'Document id: {id}',
|
||||
values: { id: metaData.id },
|
||||
})
|
||||
}
|
||||
metaData={metaData}
|
||||
/>
|
||||
{compactCard && (
|
||||
<ResultHeader
|
||||
title={
|
||||
metaData.title ??
|
||||
i18n.translate('searchIndexDocuments.result.title.id', {
|
||||
defaultMessage: 'Document id: {id}',
|
||||
values: { id: metaData.id },
|
||||
})
|
||||
}
|
||||
metaData={metaData}
|
||||
/>
|
||||
)}
|
||||
{!compactCard && (
|
||||
<RichResultHeader
|
||||
showScore={showScore}
|
||||
title={
|
||||
metaData.title ??
|
||||
i18n.translate('searchIndexDocuments.result.title.id', {
|
||||
defaultMessage: 'Document id: {id}',
|
||||
values: { id: metaData.id },
|
||||
})
|
||||
}
|
||||
onTitleClick={onDocumentClick}
|
||||
metaData={metaData}
|
||||
rightSideActions={
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip position="left" content={toolTipContent}>
|
||||
<EuiButtonIcon
|
||||
iconType={isExpanded ? 'fold' : 'unfold'}
|
||||
color={isExpanded ? 'danger' : 'primary'}
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
setIsExpanded(!isExpanded);
|
||||
}}
|
||||
aria-label={tooltipText}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{!compactCard &&
|
||||
((isExpanded && fields.length > 0) ||
|
||||
(!isExpanded && fields.slice(0, defaultVisibleFields).length > 0)) && (
|
||||
<EuiSpacer size="l" />
|
||||
)}
|
||||
<ResultFields
|
||||
isExpanded={isExpanded}
|
||||
fields={isExpanded ? fields : fields.slice(0, 3)}
|
||||
fields={isExpanded ? fields : fields.slice(0, defaultVisibleFields)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div className="resultExpandColumn">
|
||||
<EuiToolTip position="left" content={toolTipContent}>
|
||||
<EuiButtonIcon
|
||||
iconType={isExpanded ? 'fold' : 'unfold'}
|
||||
color="text"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
aria-label={tooltipText}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
{compactCard && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<div className="resultExpandColumn">
|
||||
<EuiToolTip position="left" content={toolTipContent}>
|
||||
<EuiButtonIcon
|
||||
iconType={isExpanded ? 'fold' : 'unfold'}
|
||||
color="text"
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
setIsExpanded(!isExpanded);
|
||||
}}
|
||||
aria-label={tooltipText}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { MappingProperty, SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type {
|
||||
IndicesGetMappingResponse,
|
||||
MappingProperty,
|
||||
SearchHit,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { MetaDataProps } from './result_types';
|
||||
|
||||
const TITLE_KEYS = ['title', 'name'];
|
||||
|
@ -37,15 +41,19 @@ export const resultTitle = (result: SearchHit): string | undefined => {
|
|||
export const resultMetaData = (result: SearchHit): MetaDataProps => ({
|
||||
id: result._id!,
|
||||
title: resultTitle(result),
|
||||
score: result._score,
|
||||
});
|
||||
|
||||
export const resultToField = (result: SearchHit, mappings?: Record<string, MappingProperty>) => {
|
||||
if (mappings && result._source && !Array.isArray(result._source)) {
|
||||
export const resultToFieldFromMappingResponse = (
|
||||
result: SearchHit,
|
||||
mappings?: IndicesGetMappingResponse
|
||||
) => {
|
||||
if (mappings && mappings[result._index] && result._source && !Array.isArray(result._source)) {
|
||||
if (typeof result._source === 'object') {
|
||||
return Object.entries(result._source).map(([key, value]) => {
|
||||
return {
|
||||
fieldName: key,
|
||||
fieldType: mappings[key]?.type ?? 'object',
|
||||
fieldType: mappings[result._index]?.mappings?.properties?.[key]?.type ?? 'object',
|
||||
fieldValue: JSON.stringify(value, null, 2),
|
||||
};
|
||||
});
|
||||
|
@ -53,3 +61,19 @@ export const resultToField = (result: SearchHit, mappings?: Record<string, Mappi
|
|||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const resultToFieldFromMappings = (
|
||||
result: SearchHit,
|
||||
mappings?: Record<string, MappingProperty>
|
||||
) => {
|
||||
if (mappings && result._source && !Array.isArray(result._source)) {
|
||||
return Object.entries(result._source).map(([key, value]) => {
|
||||
return {
|
||||
fieldName: key,
|
||||
fieldType: mappings[key]?.type ?? 'object',
|
||||
fieldValue: JSON.stringify(value, null, 2),
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { IconType } from '@elastic/eui';
|
||||
|
||||
export interface ResultFieldProps {
|
||||
|
@ -20,4 +21,6 @@ export interface MetaDataProps {
|
|||
id: string;
|
||||
onDocumentDelete?: Function;
|
||||
title?: string;
|
||||
score?: SearchHit['_score'];
|
||||
showScore?: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiPopoverFooter,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiTitle,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import { MetaDataProps } from './result_types';
|
||||
|
||||
interface Props {
|
||||
metaData: MetaDataProps;
|
||||
title: string;
|
||||
rightSideActions?: React.ReactNode;
|
||||
showScore?: boolean;
|
||||
onTitleClick?: () => void;
|
||||
}
|
||||
|
||||
interface TermDef {
|
||||
label: string | number;
|
||||
}
|
||||
|
||||
const Term: React.FC<TermDef> = ({ label }) => (
|
||||
<EuiFlexItem grow={false}>
|
||||
<strong>
|
||||
<EuiTextColor color="subdued">{label}:</EuiTextColor>
|
||||
</strong>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
const Definition: React.FC<TermDef> = ({ label }) => (
|
||||
<EuiFlexItem>
|
||||
<EuiTextColor color="subdued">{label}</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
const MetadataPopover: React.FC<MetaDataProps> = ({
|
||||
id,
|
||||
onDocumentDelete,
|
||||
score,
|
||||
showScore = false,
|
||||
}) => {
|
||||
const [popoverIsOpen, setPopoverIsOpen] = useState(false);
|
||||
const closePopover = () => setPopoverIsOpen(false);
|
||||
|
||||
const metaDataIcon = (
|
||||
<EuiButtonIcon
|
||||
display="empty"
|
||||
size="xs"
|
||||
iconType="iInCircle"
|
||||
color="primary"
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
setPopoverIsOpen(!popoverIsOpen);
|
||||
}}
|
||||
aria-label={i18n.translate('searchIndexDocuments.result.header.metadata.icon.ariaLabel', {
|
||||
defaultMessage: 'Metadata for document: {id}',
|
||||
values: { id },
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover button={metaDataIcon} isOpen={popoverIsOpen} closePopover={closePopover}>
|
||||
<EuiPopoverTitle>
|
||||
{i18n.translate('searchIndexDocuments.result.header.metadata.title', {
|
||||
defaultMessage: 'Document metadata',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
direction="column"
|
||||
css={css`
|
||||
width: 20rem;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="s">
|
||||
<Term label="ID" />
|
||||
<Definition label={id} />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{score && showScore && (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="s">
|
||||
<Term
|
||||
label={i18n.translate('searchIndexDocuments.result.header.metadata.score', {
|
||||
defaultMessage: 'Score',
|
||||
})}
|
||||
/>
|
||||
<Definition label={score?.toString()} />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{onDocumentDelete && (
|
||||
<EuiPopoverFooter>
|
||||
<EuiButton
|
||||
iconType="trash"
|
||||
color="danger"
|
||||
size="s"
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
closePopover();
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
{i18n.translate('searchIndexDocuments.result.header.metadata.deleteDocument', {
|
||||
defaultMessage: 'Delete document',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiPopoverFooter>
|
||||
)}
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
const Score: React.FC<{ score: MetaDataProps['score'] }> = ({ score }) => {
|
||||
return (
|
||||
<EuiPanel paddingSize="xs" hasShadow={false} color="subdued" grow>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem grow>
|
||||
<EuiIcon type="visGauge" size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
hasBorder={false}
|
||||
css={css`
|
||||
inline-size: 5ch;
|
||||
max-inline-size: 100%;
|
||||
`}
|
||||
paddingSize="none"
|
||||
color="subdued"
|
||||
>
|
||||
<EuiText textAlign="center" size="xs">
|
||||
{score ? score.toString().substring(0, 5) : '-'}
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const RichResultHeader: React.FC<Props> = ({
|
||||
title,
|
||||
metaData,
|
||||
rightSideActions = null,
|
||||
showScore = false,
|
||||
onTitleClick,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<EuiFlexItem
|
||||
grow
|
||||
css={css`
|
||||
min-height: ${euiTheme.base * 3}px;
|
||||
max-height: ${euiTheme.base * 8}px;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
{showScore && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<Score score={metaData.score} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="l" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
{onTitleClick ? (
|
||||
<EuiLink onClick={onTitleClick} color="text">
|
||||
<EuiTitle size="xs">
|
||||
<h4>{title}</h4>
|
||||
</EuiTitle>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiTitle size="xs">
|
||||
<h4>{title}</h4>
|
||||
</EuiTitle>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!!metaData && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<MetadataPopover {...metaData} showScore={showScore} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{rightSideActions}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -122,7 +122,7 @@ export const SearchIndexDocuments: React.FC = () => {
|
|||
docs={docs}
|
||||
docsPerPage={pagination.pageSize ?? 10}
|
||||
isLoading={status !== Status.SUCCESS && mappingStatus !== Status.SUCCESS}
|
||||
mappings={mappingData?.mappings?.properties ?? {}}
|
||||
mappings={mappingData ? { [indexName]: mappingData } : undefined}
|
||||
meta={data?.meta ?? DEFAULT_PAGINATION}
|
||||
onPaginate={(pageIndex) => setPagination({ ...pagination, pageIndex })}
|
||||
setDocsPerPage={(pageSize) => setPagination({ ...pagination, pageSize })}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { MappingProperty, SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import { Result, resultToField, resultMetaData } from '@kbn/search-index-documents';
|
||||
import { Result, resultMetaData, resultToField } from '@kbn/search-index-documents';
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ export enum APIRoutes {
|
|||
POST_QUERY_SOURCE_FIELDS = '/internal/search_playground/query_source_fields',
|
||||
GET_INDICES = '/internal/search_playground/indices',
|
||||
POST_SEARCH_QUERY = '/internal/search_playground/search',
|
||||
GET_INDEX_MAPPINGS = '/internal/search_playground/mappings',
|
||||
}
|
||||
|
||||
export enum LLMs {
|
||||
|
|
|
@ -106,7 +106,11 @@ export const App: React.FC<AppProps> = ({
|
|||
css={{
|
||||
position: 'relative',
|
||||
}}
|
||||
contentProps={{ css: { display: 'flex', flexGrow: 1, position: 'absolute', inset: 0 } }}
|
||||
contentProps={
|
||||
selectedPageMode === PlaygroundPageMode.search && selectedMode === 'chat'
|
||||
? undefined
|
||||
: { css: { display: 'flex', flexGrow: 1, position: 'absolute', inset: 0 } }
|
||||
}
|
||||
paddingSize={paddingSize}
|
||||
className="eui-fullHeight"
|
||||
>
|
||||
|
|
|
@ -1,34 +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 React from 'react';
|
||||
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export interface EmptyResultsArgs {
|
||||
query?: string;
|
||||
}
|
||||
|
||||
export const EmptyResults: React.FC<EmptyResultsArgs> = ({ query }) => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
body={
|
||||
<p>
|
||||
{query
|
||||
? i18n.translate('xpack.searchPlayground.resultList.emptyWithQuery.text', {
|
||||
defaultMessage: 'No result found for: {query}',
|
||||
values: { query },
|
||||
})
|
||||
: i18n.translate('xpack.searchPlayground.resultList.empty.text', {
|
||||
defaultMessage: 'No results found',
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -7,40 +7,29 @@
|
|||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPagination,
|
||||
EuiPanel,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public';
|
||||
import { DocumentList, pageToPagination } from '@kbn/search-index-documents';
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { EsHitRecord } from '@kbn/discover-utils/types';
|
||||
import type { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { IndicesGetMappingResponse, SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { buildDataTableRecord } from '@kbn/discover-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Pagination } from '../../types';
|
||||
import { getPageCounts } from '../../utils/pagination_helper';
|
||||
import { EmptyResults } from './empty_results';
|
||||
import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public';
|
||||
import { Pagination as PaginationTypeEui } from '@elastic/eui';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import { Pagination } from '../../types';
|
||||
|
||||
export interface ResultListArgs {
|
||||
searchResults: SearchHit[];
|
||||
mappings?: IndicesGetMappingResponse;
|
||||
pagination: Pagination;
|
||||
onPaginationChange: (nextPage: number) => void;
|
||||
searchQuery?: string;
|
||||
}
|
||||
|
||||
export const ResultList: React.FC<ResultListArgs> = ({
|
||||
searchResults,
|
||||
mappings,
|
||||
pagination,
|
||||
onPaginationChange,
|
||||
searchQuery = '',
|
||||
}) => {
|
||||
const {
|
||||
services: { data },
|
||||
|
@ -50,73 +39,42 @@ export const ResultList: React.FC<ResultListArgs> = ({
|
|||
data.dataViews.getDefaultDataView().then((d) => setDataView(d));
|
||||
}, [data]);
|
||||
const [flyoutDocId, setFlyoutDocId] = useState<string | undefined>(undefined);
|
||||
const { totalPage, page } = getPageCounts(pagination);
|
||||
const documentMeta: PaginationTypeEui = pageToPagination(pagination);
|
||||
const hit =
|
||||
flyoutDocId &&
|
||||
buildDataTableRecord(searchResults.find((item) => item._id === flyoutDocId) as EsHitRecord);
|
||||
return (
|
||||
<EuiPanel grow={false}>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
{searchResults.length === 0 && (
|
||||
<EuiFlexItem>
|
||||
<EmptyResults query={searchQuery} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{searchResults.length !== 0 &&
|
||||
searchResults.map((item, index) => {
|
||||
return (
|
||||
<>
|
||||
<EuiFlexItem
|
||||
key={item._id + '-' + index}
|
||||
onClick={() => setFlyoutDocId(item._id)}
|
||||
grow
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem grow>
|
||||
<EuiTitle size="xs">
|
||||
<h3>ID:{item._id}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate('xpack.searchPlayground.resultList.result.score', {
|
||||
defaultMessage: 'Document score: {score}',
|
||||
values: { score: item._score },
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{index !== searchResults.length - 1 && <EuiHorizontalRule margin="m" />}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
{searchResults.length !== 0 && (
|
||||
<EuiFlexItem>
|
||||
<EuiPagination
|
||||
pageCount={totalPage}
|
||||
activePage={page}
|
||||
onPageClick={onPaginationChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{flyoutDocId && dataView && hit && (
|
||||
<UnifiedDocViewerFlyout
|
||||
services={{}}
|
||||
onClose={() => setFlyoutDocId(undefined)}
|
||||
isEsqlQuery={false}
|
||||
columns={[]}
|
||||
hit={hit}
|
||||
dataView={dataView}
|
||||
onAddColumn={() => {}}
|
||||
onRemoveColumn={() => {}}
|
||||
setExpandedDoc={() => {}}
|
||||
flyoutType="overlay"
|
||||
/>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<>
|
||||
<DocumentList
|
||||
dataTelemetryIdPrefix="result-list"
|
||||
docs={searchResults}
|
||||
docsPerPage={10}
|
||||
isLoading={false}
|
||||
mappings={mappings}
|
||||
meta={documentMeta}
|
||||
onPaginate={onPaginationChange}
|
||||
onDocumentClick={(searchHit: SearchHit) => setFlyoutDocId(searchHit._id)}
|
||||
resultProps={{
|
||||
showScore: true,
|
||||
compactCard: false,
|
||||
defaultVisibleFields: 0,
|
||||
}}
|
||||
/>
|
||||
|
||||
{flyoutDocId && dataView && hit && (
|
||||
<UnifiedDocViewerFlyout
|
||||
services={{}}
|
||||
onClose={() => setFlyoutDocId(undefined)}
|
||||
isEsqlQuery={false}
|
||||
columns={[]}
|
||||
hit={hit}
|
||||
dataView={dataView}
|
||||
onAddColumn={() => {}}
|
||||
onRemoveColumn={() => {}}
|
||||
setExpandedDoc={() => {}}
|
||||
flyoutType="overlay"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@ import { ResultList } from './result_list';
|
|||
import { ChatForm, ChatFormFields, Pagination } from '../../types';
|
||||
import { useSearchPreview } from '../../hooks/use_search_preview';
|
||||
import { getPaginationFromPage } from '../../utils/pagination_helper';
|
||||
import { useIndexMappings } from '../../hooks/use_index_mappings';
|
||||
|
||||
export const SearchMode: React.FC = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -40,6 +41,7 @@ export const SearchMode: React.FC = () => {
|
|||
}>({ query: searchBarValue, pagination: DEFAULT_PAGINATION });
|
||||
|
||||
const { results, pagination } = useSearchPreview(searchQuery);
|
||||
const { data: mappingData } = useIndexMappings();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const handleSearch = async (query = searchBarValue, paginationParam = DEFAULT_PAGINATION) => {
|
||||
|
@ -81,15 +83,15 @@ export const SearchMode: React.FC = () => {
|
|||
/>
|
||||
</EuiForm>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="eui-yScroll">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
{searchQuery.query ? (
|
||||
<ResultList
|
||||
searchResults={results}
|
||||
mappings={mappingData}
|
||||
pagination={pagination}
|
||||
onPaginationChange={onPagination}
|
||||
searchQuery={searchQuery.query}
|
||||
/>
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import type { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { IndicesGetMappingResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { APIRoutes, ChatForm, ChatFormFields } from '../types';
|
||||
import { useKibana } from './use_kibana';
|
||||
|
||||
export interface FetchIndexMappingsArgs {
|
||||
indices: ChatForm[ChatFormFields.indices];
|
||||
http: HttpSetup;
|
||||
}
|
||||
|
||||
export const fetchIndexMappings = async ({ indices, http }: FetchIndexMappingsArgs) => {
|
||||
const mappings = await http.post<{
|
||||
mappings: IndicesGetMappingResponse;
|
||||
}>(APIRoutes.GET_INDEX_MAPPINGS, {
|
||||
body: JSON.stringify({
|
||||
indices,
|
||||
}),
|
||||
});
|
||||
return mappings;
|
||||
};
|
||||
export const useIndexMappings = () => {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const { getValues } = useFormContext();
|
||||
const indices = getValues(ChatFormFields.indices);
|
||||
const { data } = useQuery({
|
||||
queryKey: ['search-playground-index-mappings'],
|
||||
queryFn: () => fetchIndexMappings({ indices, http }),
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
});
|
||||
|
||||
return { data: data?.mappings };
|
||||
};
|
|
@ -8,6 +8,7 @@
|
|||
import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import type { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { APIRoutes, ChatForm, ChatFormFields, Pagination } from '../types';
|
||||
import { useKibana } from './use_kibana';
|
||||
import { DEFAULT_PAGINATION } from '../../common';
|
||||
|
@ -17,7 +18,7 @@ export interface FetchSearchResultsArgs {
|
|||
pagination: Pagination;
|
||||
indices: ChatForm[ChatFormFields.indices];
|
||||
elasticsearchQuery: ChatForm[ChatFormFields.elasticsearchQuery];
|
||||
http: ReturnType<typeof useKibana>['services']['http'];
|
||||
http: HttpSetup;
|
||||
}
|
||||
|
||||
interface UseSearchPreviewData {
|
||||
|
@ -64,9 +65,10 @@ export const useSearchPreview = ({
|
|||
query: string;
|
||||
pagination: Pagination;
|
||||
}) => {
|
||||
const { services } = useKibana();
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana();
|
||||
const { getValues } = useFormContext();
|
||||
const { http } = services;
|
||||
const indices = getValues(ChatFormFields.indices);
|
||||
const elasticsearchQuery = getValues(ChatFormFields.elasticsearchQuery);
|
||||
|
||||
|
|
|
@ -7,13 +7,6 @@
|
|||
|
||||
import { Pagination } from '../../common/types';
|
||||
|
||||
export const getPageCounts = (pagination: Pagination) => {
|
||||
const { total, from, size } = pagination;
|
||||
const totalPage = Math.ceil(total / size);
|
||||
const page = Math.floor(from / size);
|
||||
return { totalPage, total, page, size };
|
||||
};
|
||||
|
||||
export const getPaginationFromPage = (page: number, size: number, previousValue: Pagination) => {
|
||||
const from = page < 0 ? 0 : page * size;
|
||||
return { ...previousValue, from, size, page };
|
||||
|
|
|
@ -305,4 +305,47 @@ export function defineRoutes({
|
|||
}
|
||||
})
|
||||
);
|
||||
router.post(
|
||||
{
|
||||
path: APIRoutes.GET_INDEX_MAPPINGS,
|
||||
validate: {
|
||||
body: schema.object({
|
||||
indices: schema.arrayOf(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
errorHandler(logger)(async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const { indices } = request.body;
|
||||
|
||||
try {
|
||||
if (indices.length === 0) {
|
||||
return response.badRequest({
|
||||
body: {
|
||||
message: 'Indices cannot be empty',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const mappings = await client.asCurrentUser.indices.getMapping({
|
||||
index: indices,
|
||||
});
|
||||
return response.ok({
|
||||
body: {
|
||||
mappings,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to get index mappings', e);
|
||||
if (typeof e === 'object' && e.message) {
|
||||
return response.badRequest({
|
||||
body: {
|
||||
message: e.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@
|
|||
"@kbn/unified-doc-viewer-plugin",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/discover-utils",
|
||||
"@kbn/data-plugin"
|
||||
"@kbn/data-plugin",
|
||||
"@kbn/search-index-documents"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -63,7 +63,7 @@ export const IndexDocuments: React.FC<IndexDocumentsProps> = ({ indexName }) =>
|
|||
docs={docs}
|
||||
docsPerPage={pagination.pageSize ?? 10}
|
||||
isLoading={false}
|
||||
mappings={mappingData?.mappings?.properties ?? {}}
|
||||
mappings={mappingData ? { [indexName]: mappingData } : undefined}
|
||||
meta={documentsMeta ?? DEFAULT_PAGINATION}
|
||||
onPaginate={(pageIndex) => setPagination({ ...pagination, pageIndex })}
|
||||
setDocsPerPage={(pageSize) => setPagination({ ...pagination, pageSize })}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue