From 272f3bb07fe7c5671afaaa439dd198e49a6cb631 Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Wed, 2 Oct 2024 23:57:21 +1000
Subject: [PATCH] [8.x] Update Search Index Document Card design. (#194061)
(#194555)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Backport
This will backport the following commits from `main` to `8.x`:
- [Update Search Index Document Card design.
(#194061)](https://github.com/elastic/kibana/pull/194061)
### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)
Co-authored-by: Efe Gürkan YALAMAN
Co-authored-by: Elastic Machine
Co-authored-by: Alex Szabo
---
.../components/document_list.tsx | 163 +++++++------
.../components/documents_list.test.tsx | 10 +-
.../components/result/index.ts | 6 +-
.../components/result/result.tsx | 135 ++++++++---
.../components/result/result_metadata.ts | 32 ++-
.../components/result/result_types.ts | 3 +
.../components/result/rich_result_header.tsx | 228 ++++++++++++++++++
.../components/search_index/documents.tsx | 2 +-
.../index_documents/document_list.tsx | 2 +-
.../plugins/search_playground/common/types.ts | 1 +
.../public/components/app.tsx | 6 +-
.../components/search_mode/empty_results.tsx | 34 ---
.../components/search_mode/result_list.tsx | 122 +++-------
.../components/search_mode/search_mode.tsx | 6 +-
.../public/hooks/use_index_mappings.ts | 45 ++++
.../public/hooks/use_search_preview.ts | 8 +-
.../public/utils/pagination_helper.ts | 7 -
.../search_playground/server/routes.ts | 43 ++++
.../plugins/search_playground/tsconfig.json | 3 +-
.../components/index_documents/documents.tsx | 2 +-
20 files changed, 611 insertions(+), 247 deletions(-)
create mode 100644 packages/kbn-search-index-documents/components/result/rich_result_header.tsx
delete mode 100644 x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx
create mode 100644 x-pack/plugins/search_playground/public/hooks/use_index_mappings.ts
diff --git a/packages/kbn-search-index-documents/components/document_list.tsx b/packages/kbn-search-index-documents/components/document_list.tsx
index ec9efe7d6b1d7..5491d228e7d07 100644
--- a/packages/kbn-search-index-documents/components/document_list.tsx
+++ b/packages/kbn-search-index-documents/components/document_list.tsx
@@ -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 | undefined;
+ mappings: IndicesGetMappingResponse | undefined;
meta: Pagination;
onPaginate: (newPageIndex: number) => void;
- setDocsPerPage: (docsPerPage: number) => void;
+ setDocsPerPage?: (docsPerPage: number) => void;
+ onDocumentClick?: (doc: SearchHit) => void;
+ resultProps?: Partial;
}
export const DocumentList: React.FC = ({
@@ -53,6 +57,8 @@ export const DocumentList: React.FC = ({
meta,
onPaginate,
setDocsPerPage,
+ onDocumentClick,
+ resultProps = {},
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@@ -99,7 +105,12 @@ export const DocumentList: React.FC = ({
{docs.map((doc) => {
return (
-
+ onDocumentClick(doc) : undefined}
+ {...resultProps}
+ />
);
@@ -116,81 +127,83 @@ export const DocumentList: React.FC = ({
onPageClick={onPaginate}
/>
-
- {
- setIsPopoverOpen(true);
- }}
- >
- {i18n.translate('searchIndexDocuments.documentList.pagination.itemsPerPage', {
- defaultMessage: 'Documents per page: {docPerPage}',
- values: { docPerPage: docsPerPage },
- })}
-
- }
- isOpen={isPopoverOpen}
- closePopover={() => {
- setIsPopoverOpen(false);
- }}
- panelPaddingSize="none"
- anchorPosition="downLeft"
- >
-
+ {
- setIsPopoverOpen(false);
- setDocsPerPage(10);
+ setIsPopoverOpen(true);
}}
>
- {i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
- defaultMessage: '{docCount} documents',
- values: { docCount: 10 },
+ {i18n.translate('searchIndexDocuments.documentList.pagination.itemsPerPage', {
+ defaultMessage: 'Documents per page: {docPerPage}',
+ values: { docPerPage: docsPerPage },
})}
- ,
+
+ }
+ isOpen={isPopoverOpen}
+ closePopover={() => {
+ setIsPopoverOpen(false);
+ }}
+ panelPaddingSize="none"
+ anchorPosition="downLeft"
+ >
+ {
+ setIsPopoverOpen(false);
+ setDocsPerPage(10);
+ }}
+ >
+ {i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
+ defaultMessage: '{docCount} documents',
+ values: { docCount: 10 },
+ })}
+ ,
- {
- setIsPopoverOpen(false);
- setDocsPerPage(25);
- }}
- >
- {i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
- defaultMessage: '{docCount} documents',
- values: { docCount: 25 },
- })}
- ,
- {
- setIsPopoverOpen(false);
- setDocsPerPage(50);
- }}
- >
- {i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
- defaultMessage: '{docCount} documents',
- values: { docCount: 50 },
- })}
- ,
- ]}
- />
-
-
+ {
+ setIsPopoverOpen(false);
+ setDocsPerPage(25);
+ }}
+ >
+ {i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
+ defaultMessage: '{docCount} documents',
+ values: { docCount: 25 },
+ })}
+ ,
+ {
+ setIsPopoverOpen(false);
+ setDocsPerPage(50);
+ }}
+ >
+ {i18n.translate('searchIndexDocuments.documentList.paginationOptions.option', {
+ defaultMessage: '{docCount} documents',
+ values: { docCount: 50 },
+ })}
+ ,
+ ]}
+ />
+
+
+ )}
diff --git a/packages/kbn-search-index-documents/components/documents_list.test.tsx b/packages/kbn-search-index-documents/components/documents_list.test.tsx
index b445c5fa48711..b97b36989ad62 100644
--- a/packages/kbn-search-index-documents/components/documents_list.test.tsx
+++ b/packages/kbn-search-index-documents/components/documents_list.test.tsx
@@ -58,8 +58,14 @@ describe('DocumentList', () => {
},
],
mappings: {
- AvgTicketPrice: {
- type: 'float' as const,
+ kibana_sample_data_flights: {
+ mappings: {
+ properties: {
+ AvgTicketPrice: {
+ type: 'float' as const,
+ },
+ },
+ },
},
},
};
diff --git a/packages/kbn-search-index-documents/components/result/index.ts b/packages/kbn-search-index-documents/components/result/index.ts
index a5e613fbd83ec..8c894f7e2ca3b 100644
--- a/packages/kbn-search-index-documents/components/result/index.ts
+++ b/packages/kbn-search-index-documents/components/result/index.ts
@@ -8,4 +8,8 @@
*/
export { Result } from './result';
-export { resultMetaData, resultToField } from './result_metadata';
+export {
+ resultMetaData,
+ resultToFieldFromMappingResponse,
+ resultToFieldFromMappings as resultToField,
+} from './result_metadata';
diff --git a/packages/kbn-search-index-documents/components/result/result.tsx b/packages/kbn-search-index-documents/components/result/result.tsx
index 4e6f0ed8c6eb8..5e1c4db104116 100644
--- a/packages/kbn-search-index-documents/components/result/result.tsx
+++ b/packages/kbn-search-index-documents/components/result/result.tsx
@@ -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';
+
+export const DEFAULT_VISIBLE_FIELDS = 3;
-interface ResultProps {
+export interface ResultProps {
fields: ResultFieldProps[];
metaData: MetaDataProps;
+ defaultVisibleFields?: number;
+ showScore?: boolean;
+ compactCard?: boolean;
+ onDocumentClick?: () => void;
}
-export const Result: React.FC = ({ metaData, fields }) => {
+export const Result: React.FC = ({
+ 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 (
-
+
-
+
-
+ {compactCard && (
+
+ )}
+ {!compactCard && (
+
+
+ ) => {
+ e.stopPropagation();
+ setIsExpanded(!isExpanded);
+ }}
+ aria-label={tooltipText}
+ />
+
+
+ }
+ />
+ )}
+ {!compactCard &&
+ ((isExpanded && fields.length > 0) ||
+ (!isExpanded && fields.slice(0, defaultVisibleFields).length > 0)) && (
+
+ )}
-
-
-
- setIsExpanded(!isExpanded)}
- aria-label={tooltipText}
- />
-
-
-
+ {compactCard && (
+
+
+
+ ) => {
+ e.stopPropagation();
+ setIsExpanded(!isExpanded);
+ }}
+ aria-label={tooltipText}
+ />
+
+
+
+ )}
);
diff --git a/packages/kbn-search-index-documents/components/result/result_metadata.ts b/packages/kbn-search-index-documents/components/result/result_metadata.ts
index 783cd537b4535..ba50644cafc59 100644
--- a/packages/kbn-search-index-documents/components/result/result_metadata.ts
+++ b/packages/kbn-search-index-documents/components/result/result_metadata.ts
@@ -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) => {
- 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
+) => {
+ 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 [];
+};
diff --git a/packages/kbn-search-index-documents/components/result/result_types.ts b/packages/kbn-search-index-documents/components/result/result_types.ts
index e04ccb1091c8e..c7899874b27ee 100644
--- a/packages/kbn-search-index-documents/components/result/result_types.ts
+++ b/packages/kbn-search-index-documents/components/result/result_types.ts
@@ -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;
}
diff --git a/packages/kbn-search-index-documents/components/result/rich_result_header.tsx b/packages/kbn-search-index-documents/components/result/rich_result_header.tsx
new file mode 100644
index 0000000000000..7caff8514871f
--- /dev/null
+++ b/packages/kbn-search-index-documents/components/result/rich_result_header.tsx
@@ -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 = ({ label }) => (
+
+
+ {label}:
+
+
+);
+
+const Definition: React.FC = ({ label }) => (
+
+ {label}
+
+);
+const MetadataPopover: React.FC = ({
+ id,
+ onDocumentDelete,
+ score,
+ showScore = false,
+}) => {
+ const [popoverIsOpen, setPopoverIsOpen] = useState(false);
+ const closePopover = () => setPopoverIsOpen(false);
+
+ const metaDataIcon = (
+ ) => {
+ e.stopPropagation();
+ setPopoverIsOpen(!popoverIsOpen);
+ }}
+ aria-label={i18n.translate('searchIndexDocuments.result.header.metadata.icon.ariaLabel', {
+ defaultMessage: 'Metadata for document: {id}',
+ values: { id },
+ })}
+ />
+ );
+
+ return (
+
+
+ {i18n.translate('searchIndexDocuments.result.header.metadata.title', {
+ defaultMessage: 'Document metadata',
+ })}
+
+
+
+
+
+
+
+
+
+ {score && showScore && (
+
+
+
+
+
+
+ )}
+
+ {onDocumentDelete && (
+
+ ) => {
+ e.stopPropagation();
+ closePopover();
+ }}
+ fullWidth
+ >
+ {i18n.translate('searchIndexDocuments.result.header.metadata.deleteDocument', {
+ defaultMessage: 'Delete document',
+ })}
+
+
+ )}
+
+ );
+};
+
+const Score: React.FC<{ score: MetaDataProps['score'] }> = ({ score }) => {
+ return (
+
+
+
+
+
+
+
+
+ {score ? score.toString().substring(0, 5) : '-'}
+
+
+
+
+
+ );
+};
+
+export const RichResultHeader: React.FC = ({
+ title,
+ metaData,
+ rightSideActions = null,
+ showScore = false,
+ onTitleClick,
+}) => {
+ const { euiTheme } = useEuiTheme();
+ return (
+
+
+ {showScore && (
+
+
+
+ )}
+
+
+
+
+
+
+ {onTitleClick ? (
+
+
+ {title}
+
+
+ ) : (
+
+ {title}
+
+ )}
+
+ {!!metaData && (
+
+
+
+ )}
+
+
+
+
+
+ {rightSideActions}
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx
index 353fc84546a9c..592da5d044f2a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents.tsx
@@ -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 })}
diff --git a/x-pack/plugins/search_indices/public/components/index_documents/document_list.tsx b/x-pack/plugins/search_indices/public/components/index_documents/document_list.tsx
index ddf4f27122ef9..22735cc86c05f 100644
--- a/x-pack/plugins/search_indices/public/components/index_documents/document_list.tsx
+++ b/x-pack/plugins/search_indices/public/components/index_documents/document_list.tsx
@@ -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';
diff --git a/x-pack/plugins/search_playground/common/types.ts b/x-pack/plugins/search_playground/common/types.ts
index c0e3300fe7dff..c239858b5b459 100644
--- a/x-pack/plugins/search_playground/common/types.ts
+++ b/x-pack/plugins/search_playground/common/types.ts
@@ -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 {
diff --git a/x-pack/plugins/search_playground/public/components/app.tsx b/x-pack/plugins/search_playground/public/components/app.tsx
index 34b89433ea705..4f371ea5d15bb 100644
--- a/x-pack/plugins/search_playground/public/components/app.tsx
+++ b/x-pack/plugins/search_playground/public/components/app.tsx
@@ -106,7 +106,11 @@ export const App: React.FC = ({
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"
>
diff --git a/x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx b/x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx
deleted file mode 100644
index ab5779e85ddd5..0000000000000
--- a/x-pack/plugins/search_playground/public/components/search_mode/empty_results.tsx
+++ /dev/null
@@ -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 = ({ query }) => {
- return (
-
- {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',
- })}
-
- }
- />
- );
-};
diff --git a/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx b/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx
index 02e1193e22332..87c7060c29151 100644
--- a/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx
+++ b/x-pack/plugins/search_playground/public/components/search_mode/result_list.tsx
@@ -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 = ({
searchResults,
+ mappings,
pagination,
onPaginationChange,
- searchQuery = '',
}) => {
const {
services: { data },
@@ -50,73 +39,42 @@ export const ResultList: React.FC = ({
data.dataViews.getDefaultDataView().then((d) => setDataView(d));
}, [data]);
const [flyoutDocId, setFlyoutDocId] = useState(undefined);
- const { totalPage, page } = getPageCounts(pagination);
+ const documentMeta: PaginationTypeEui = pageToPagination(pagination);
const hit =
flyoutDocId &&
buildDataTableRecord(searchResults.find((item) => item._id === flyoutDocId) as EsHitRecord);
return (
-
-
- {searchResults.length === 0 && (
-
-
-
- )}
- {searchResults.length !== 0 &&
- searchResults.map((item, index) => {
- return (
- <>
- setFlyoutDocId(item._id)}
- grow
- >
-
-
-
- ID:{item._id}
-
-
-
-
-
- {i18n.translate('xpack.searchPlayground.resultList.result.score', {
- defaultMessage: 'Document score: {score}',
- values: { score: item._score },
- })}
-
-
-
-
-
- {index !== searchResults.length - 1 && }
- >
- );
- })}
- {searchResults.length !== 0 && (
-
-
-
- )}
- {flyoutDocId && dataView && hit && (
- setFlyoutDocId(undefined)}
- isEsqlQuery={false}
- columns={[]}
- hit={hit}
- dataView={dataView}
- onAddColumn={() => {}}
- onRemoveColumn={() => {}}
- setExpandedDoc={() => {}}
- flyoutType="overlay"
- />
- )}
-
-
+ <>
+ setFlyoutDocId(searchHit._id)}
+ resultProps={{
+ showScore: true,
+ compactCard: false,
+ defaultVisibleFields: 0,
+ }}
+ />
+
+ {flyoutDocId && dataView && hit && (
+ setFlyoutDocId(undefined)}
+ isEsqlQuery={false}
+ columns={[]}
+ hit={hit}
+ dataView={dataView}
+ onAddColumn={() => {}}
+ onRemoveColumn={() => {}}
+ setExpandedDoc={() => {}}
+ flyoutType="overlay"
+ />
+ )}
+ >
);
};
diff --git a/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx b/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx
index 967c5786eed63..94e6337f1a03c 100644
--- a/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx
+++ b/x-pack/plugins/search_playground/public/components/search_mode/search_mode.tsx
@@ -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 = () => {
/>
-
+
{searchQuery.query ? (
) : (
{
+ 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 };
+};
diff --git a/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts b/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts
index 54566563fcee5..f66f81b37cd2e 100644
--- a/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts
+++ b/x-pack/plugins/search_playground/public/hooks/use_search_preview.ts
@@ -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['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);
diff --git a/x-pack/plugins/search_playground/public/utils/pagination_helper.ts b/x-pack/plugins/search_playground/public/utils/pagination_helper.ts
index 1379bbc257bd4..2602327b8c968 100644
--- a/x-pack/plugins/search_playground/public/utils/pagination_helper.ts
+++ b/x-pack/plugins/search_playground/public/utils/pagination_helper.ts
@@ -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 };
diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts
index d30904214d8df..cf6a139b1344b 100644
--- a/x-pack/plugins/search_playground/server/routes.ts
+++ b/x-pack/plugins/search_playground/server/routes.ts
@@ -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;
+ }
+ })
+ );
}
diff --git a/x-pack/plugins/search_playground/tsconfig.json b/x-pack/plugins/search_playground/tsconfig.json
index eebfd0df9a7b3..29c144ff4bac8 100644
--- a/x-pack/plugins/search_playground/tsconfig.json
+++ b/x-pack/plugins/search_playground/tsconfig.json
@@ -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/**/*",
diff --git a/x-pack/plugins/serverless_search/public/application/components/index_documents/documents.tsx b/x-pack/plugins/serverless_search/public/application/components/index_documents/documents.tsx
index d74c3a479a68a..0dc6e74d63ba6 100644
--- a/x-pack/plugins/serverless_search/public/application/components/index_documents/documents.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/index_documents/documents.tsx
@@ -63,7 +63,7 @@ export const IndexDocuments: React.FC = ({ 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 })}