mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Onboarding] Search detail document list tweaks (#194804)](https://github.com/elastic/kibana/pull/194804) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Joe McElroy","email":"joseph.mcelroy@elastic.co"},"sourceCommit":{"committedDate":"2024-10-11T19:09:11Z","message":"[Onboarding] Search detail document list tweaks (#194804)\n\n## Summary\r\n\r\n- Implemented priority ordering of fields. Semantic_text, dense_vector,\r\nsparse_vector comes first + special field names.\r\n- give extra information + copy action for dense_vector fields\r\n- updated the design for the non compact view\r\n\r\n\r\n\r\n\r\n \r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [ ] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### Risk Matrix\r\n\r\nDelete this section if it is not applicable to this PR.\r\n\r\nBefore closing this PR, invite QA, stakeholders, and other developers to\r\nidentify risks that should be tested prior to the change/feature\r\nrelease.\r\n\r\nWhen forming the risk matrix, consider some of the following examples\r\nand how they may potentially impact the change:\r\n\r\n| Risk | Probability | Severity | Mitigation/Notes |\r\n\r\n|---------------------------|-------------|----------|-------------------------|\r\n| Multiple Spaces—unexpected behavior in non-default Kibana Space.\r\n| Low | High | Integration tests will verify that all features are still\r\nsupported in non-default Kibana Space and when user switches between\r\nspaces. |\r\n| Multiple nodes—Elasticsearch polling might have race conditions\r\nwhen multiple Kibana nodes are polling for the same tasks. | High | Low\r\n| Tasks are idempotent, so executing them multiple times will not result\r\nin logical error, but will degrade performance. To test for this case we\r\nadd plenty of unit tests around this logic and document manual testing\r\nprocedure. |\r\n| Code should gracefully handle cases when feature X or plugin Y are\r\ndisabled. | Medium | High | Unit tests will verify that any feature flag\r\nor plugin combination still results in our service operational. |\r\n| [See more potential risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by: Julian Rosado <julian.rosado@elastic.co>","sha":"2f54dc8afda5f3881e9d20b06c3b534cdf89a141","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor"],"title":"[Onboarding] Search detail document list tweaks","number":194804,"url":"https://github.com/elastic/kibana/pull/194804","mergeCommit":{"message":"[Onboarding] Search detail document list tweaks (#194804)\n\n## Summary\r\n\r\n- Implemented priority ordering of fields. Semantic_text, dense_vector,\r\nsparse_vector comes first + special field names.\r\n- give extra information + copy action for dense_vector fields\r\n- updated the design for the non compact view\r\n\r\n\r\n\r\n\r\n \r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [ ] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### Risk Matrix\r\n\r\nDelete this section if it is not applicable to this PR.\r\n\r\nBefore closing this PR, invite QA, stakeholders, and other developers to\r\nidentify risks that should be tested prior to the change/feature\r\nrelease.\r\n\r\nWhen forming the risk matrix, consider some of the following examples\r\nand how they may potentially impact the change:\r\n\r\n| Risk | Probability | Severity | Mitigation/Notes |\r\n\r\n|---------------------------|-------------|----------|-------------------------|\r\n| Multiple Spaces—unexpected behavior in non-default Kibana Space.\r\n| Low | High | Integration tests will verify that all features are still\r\nsupported in non-default Kibana Space and when user switches between\r\nspaces. |\r\n| Multiple nodes—Elasticsearch polling might have race conditions\r\nwhen multiple Kibana nodes are polling for the same tasks. | High | Low\r\n| Tasks are idempotent, so executing them multiple times will not result\r\nin logical error, but will degrade performance. To test for this case we\r\nadd plenty of unit tests around this logic and document manual testing\r\nprocedure. |\r\n| Code should gracefully handle cases when feature X or plugin Y are\r\ndisabled. | Medium | High | Unit tests will verify that any feature flag\r\nor plugin combination still results in our service operational. |\r\n| [See more potential risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by: Julian Rosado <julian.rosado@elastic.co>","sha":"2f54dc8afda5f3881e9d20b06c3b534cdf89a141"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194804","number":194804,"mergeCommit":{"message":"[Onboarding] Search detail document list tweaks (#194804)\n\n## Summary\r\n\r\n- Implemented priority ordering of fields. Semantic_text, dense_vector,\r\nsparse_vector comes first + special field names.\r\n- give extra information + copy action for dense_vector fields\r\n- updated the design for the non compact view\r\n\r\n\r\n\r\n\r\n \r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [ ] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### Risk Matrix\r\n\r\nDelete this section if it is not applicable to this PR.\r\n\r\nBefore closing this PR, invite QA, stakeholders, and other developers to\r\nidentify risks that should be tested prior to the change/feature\r\nrelease.\r\n\r\nWhen forming the risk matrix, consider some of the following examples\r\nand how they may potentially impact the change:\r\n\r\n| Risk | Probability | Severity | Mitigation/Notes |\r\n\r\n|---------------------------|-------------|----------|-------------------------|\r\n| Multiple Spaces—unexpected behavior in non-default Kibana Space.\r\n| Low | High | Integration tests will verify that all features are still\r\nsupported in non-default Kibana Space and when user switches between\r\nspaces. |\r\n| Multiple nodes—Elasticsearch polling might have race conditions\r\nwhen multiple Kibana nodes are polling for the same tasks. | High | Low\r\n| Tasks are idempotent, so executing them multiple times will not result\r\nin logical error, but will degrade performance. To test for this case we\r\nadd plenty of unit tests around this logic and document manual testing\r\nprocedure. |\r\n| Code should gracefully handle cases when feature X or plugin Y are\r\ndisabled. | Medium | High | Unit tests will verify that any feature flag\r\nor plugin combination still results in our service operational. |\r\n| [See more potential risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by: Julian Rosado <julian.rosado@elastic.co>","sha":"2f54dc8afda5f3881e9d20b06c3b534cdf89a141"}}]}] BACKPORT--> Co-authored-by: Joe McElroy <joseph.mcelroy@elastic.co>
This commit is contained in:
parent
5eafc27e2a
commit
18d03f264d
12 changed files with 310 additions and 156 deletions
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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".
|
||||
*/
|
||||
|
||||
export const PERMANENTLY_TRUNCATED_FIELDS = ['dense_vector', 'sparse_vector'];
|
|
@ -12,4 +12,5 @@ export {
|
|||
resultMetaData,
|
||||
resultToFieldFromMappingResponse,
|
||||
resultToFieldFromMappings as resultToField,
|
||||
reorderFieldsInImportance,
|
||||
} from './result_metadata';
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
.resultField:nth-child(odd) {
|
||||
background-color: $euiColorLightestShade;
|
||||
}
|
||||
|
||||
.resultField {
|
||||
padding: $euiSizeXS $euiSizeS;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid $euiColorLightShade;
|
||||
position: relative;
|
||||
|
||||
> .euiTableRowCell {
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
>.euiTableRow:hover {
|
||||
background-color: $euiColorEmptyShade;
|
||||
}
|
||||
|
||||
>.euiTableRowCell {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
|
||||
> .euiTableCellContent {
|
||||
padding: $euiSizeXS;
|
||||
>.euiTableCellContent {
|
||||
padding: $euiSizeS;
|
||||
font-family: $euiCodeFontFamily;
|
||||
color: $euiColorMediumShade;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.denseVectorFieldValue {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: $euiSizeS;
|
||||
background-color: $euiColorEmptyShade;
|
||||
padding: 0 $euiSizeS;
|
||||
}
|
||||
|
||||
.resultExpandColumn {
|
||||
border-left: $euiBorderThin;
|
||||
align-items: flex-start;
|
||||
|
@ -31,4 +47,4 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
|
@ -13,8 +13,8 @@ import {
|
|||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiHorizontalRule,
|
||||
EuiSplitPanel,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
@ -66,99 +66,65 @@ export const Result: React.FC<ResultProps> = ({
|
|||
const toolTipContent = <>{tooltipText}</>;
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
hasBorder
|
||||
data-test-subj="search-index-documents-result"
|
||||
paddingSize={compactCard ? 's' : 'l'}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
justifyContent="spaceAround"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
{compactCard && (
|
||||
<ResultHeader
|
||||
title={
|
||||
metaData.title ??
|
||||
i18n.translate('searchIndexDocuments.result.title.id', {
|
||||
defaultMessage: 'Document id: {id}',
|
||||
values: { id: metaData.id },
|
||||
})
|
||||
}
|
||||
metaData={{
|
||||
...metaData,
|
||||
onDocumentDelete,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!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,
|
||||
onDocumentDelete,
|
||||
}}
|
||||
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, defaultVisibleFields)}
|
||||
<EuiSplitPanel.Outer hasBorder={true} data-test-subj="search-index-documents-result">
|
||||
<EuiSplitPanel.Inner paddingSize="m" color="plain" className="resultHeaderContainer">
|
||||
<EuiFlexGroup gutterSize="none" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
{compactCard && (
|
||||
<ResultHeader
|
||||
title={
|
||||
metaData.title ??
|
||||
i18n.translate('searchIndexDocuments.result.title.id', {
|
||||
defaultMessage: 'Document id: {id}',
|
||||
values: { id: metaData.id },
|
||||
})
|
||||
}
|
||||
metaData={metaData}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</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>
|
||||
)}
|
||||
|
||||
{!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,
|
||||
onDocumentDelete,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip position="left" content={toolTipContent}>
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
iconType={isExpanded ? 'fold' : 'unfold'}
|
||||
color={isExpanded ? 'danger' : 'primary'}
|
||||
data-test-subj={isExpanded ? 'documentShowLessFields' : 'documentShowMoreFields'}
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
setIsExpanded(!isExpanded);
|
||||
}}
|
||||
aria-label={tooltipText}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiSplitPanel.Inner>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiSplitPanel.Inner paddingSize="m">
|
||||
<ResultFields
|
||||
isExpanded={isExpanded}
|
||||
fields={isExpanded ? fields : fields.slice(0, defaultVisibleFields)}
|
||||
/>
|
||||
</EuiSplitPanel.Inner>
|
||||
</EuiSplitPanel.Outer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,17 +9,12 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiCodeBlock,
|
||||
EuiIcon,
|
||||
EuiTableRow,
|
||||
EuiTableRowCell,
|
||||
EuiText,
|
||||
EuiToken,
|
||||
} from '@elastic/eui';
|
||||
import { EuiTableRow, EuiTableRowCell, EuiText, EuiToken } from '@elastic/eui';
|
||||
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { ResultFieldProps } from './result_types';
|
||||
import { PERMANENTLY_TRUNCATED_FIELDS } from './constants';
|
||||
import { ResultFieldValue } from './result_field_value';
|
||||
|
||||
const iconMap: Record<string, string> = {
|
||||
boolean: 'tokenBoolean',
|
||||
|
@ -64,9 +59,11 @@ export const ResultField: React.FC<ResultFieldProps> = ({
|
|||
iconType,
|
||||
fieldName,
|
||||
fieldValue,
|
||||
fieldType,
|
||||
fieldType = 'object',
|
||||
isExpanded,
|
||||
}) => {
|
||||
const shouldTruncate = !isExpanded || PERMANENTLY_TRUNCATED_FIELDS.includes(fieldType);
|
||||
|
||||
return (
|
||||
<EuiTableRow className="resultField">
|
||||
<EuiTableRowCell className="resultFieldRowCell" width={euiThemeVars.euiSizeL} valign="middle">
|
||||
|
@ -79,31 +76,16 @@ export const ResultField: React.FC<ResultFieldProps> = ({
|
|||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
className="resultFieldRowCell"
|
||||
width="25%"
|
||||
width="20%"
|
||||
truncateText={!isExpanded}
|
||||
valign="middle"
|
||||
>
|
||||
<EuiText size="xs">{fieldName}</EuiText>
|
||||
<EuiText size="s" color="default">
|
||||
{fieldName}
|
||||
</EuiText>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell
|
||||
className="resultFieldRowCell"
|
||||
width={euiThemeVars.euiSizeXXL}
|
||||
valign="middle"
|
||||
>
|
||||
<EuiIcon type="sortRight" color="subdued" />
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell className="resultFieldRowCell" truncateText={!isExpanded} valign="middle">
|
||||
{(fieldType === 'object' ||
|
||||
fieldType === 'array' ||
|
||||
fieldType === 'nested' ||
|
||||
Array.isArray(fieldValue)) &&
|
||||
isExpanded ? (
|
||||
<EuiCodeBlock language="json" overflowHeight="250" transparentBackground>
|
||||
{fieldValue}
|
||||
</EuiCodeBlock>
|
||||
) : (
|
||||
<EuiText size="xs">{fieldValue}</EuiText>
|
||||
)}
|
||||
<EuiTableRowCell className="resultFieldRowCell" truncateText={shouldTruncate} valign="middle">
|
||||
<ResultFieldValue fieldValue={fieldValue} fieldType={fieldType} isExpanded={isExpanded} />
|
||||
</EuiTableRowCell>
|
||||
</EuiTableRow>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiCodeBlock,
|
||||
EuiCopy,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PERMANENTLY_TRUNCATED_FIELDS } from './constants';
|
||||
|
||||
interface ResultFieldValueProps {
|
||||
fieldValue: string;
|
||||
fieldType: string;
|
||||
isExpanded?: boolean;
|
||||
}
|
||||
|
||||
export const ResultFieldValue: React.FC<ResultFieldValueProps> = ({
|
||||
fieldValue,
|
||||
fieldType,
|
||||
isExpanded = false,
|
||||
}) => {
|
||||
if (
|
||||
isExpanded &&
|
||||
fieldType &&
|
||||
(['object', 'array', 'nested'].includes(fieldType) || Array.isArray(fieldValue))
|
||||
) {
|
||||
return (
|
||||
<EuiCodeBlock language="json" transparentBackground fontSize="s">
|
||||
{fieldValue}
|
||||
</EuiCodeBlock>
|
||||
);
|
||||
} else if (PERMANENTLY_TRUNCATED_FIELDS.includes(fieldType)) {
|
||||
return (
|
||||
<>
|
||||
<EuiText size="s" color="default">
|
||||
{fieldValue}
|
||||
</EuiText>
|
||||
{fieldType === 'dense_vector' && (
|
||||
<div className={'denseVectorFieldValue'}>
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('searchIndexDocuments.result.value.denseVector.dimLabel', {
|
||||
defaultMessage: '{value} dims',
|
||||
values: {
|
||||
value: JSON.parse(fieldValue).length,
|
||||
},
|
||||
})}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCopy textToCopy={fieldValue}>
|
||||
{(copy) => (
|
||||
<EuiIcon
|
||||
type="copyClipboard"
|
||||
onClick={copy}
|
||||
data-test-subj="copyDenseVector"
|
||||
aria-label={i18n.translate(
|
||||
'searchIndexDocuments.result.value.denseVector.copy',
|
||||
{
|
||||
defaultMessage: 'Copy vector',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiText size="s" color="default">
|
||||
{fieldValue}
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -17,8 +17,8 @@ import {
|
|||
EuiPopover,
|
||||
EuiPopoverFooter,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -95,18 +95,18 @@ const MetadataPopover: React.FC<MetaDataProps> = ({ id, onDocumentDelete }) => {
|
|||
export const ResultHeader: React.FC<Props> = ({ title, metaData }) => {
|
||||
return (
|
||||
<div className="resultHeader">
|
||||
<EuiText size="s">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<strong>{title}</strong>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h4>{title}</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{!!metaData && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<MetadataPopover {...metaData} />
|
||||
</EuiFlexItem>
|
||||
{!!metaData && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<MetadataPopover {...metaData} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import { resultTitle } from './result_metadata';
|
||||
import { reorderFieldsInImportance, resultTitle } from './result_metadata';
|
||||
import { FieldProps } from './result_types';
|
||||
|
||||
const makeSearchHit = (source: undefined | unknown): SearchHit =>
|
||||
({
|
||||
|
@ -27,3 +28,45 @@ describe('resultTitle', () => {
|
|||
expect(resultTitle(makeSearchHit(undefined))).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reorderFieldsInImportance', () => {
|
||||
it('sorts fields by type and name', () => {
|
||||
const fields: FieldProps[] = [
|
||||
{ fieldName: 'field6', fieldType: 'sparse_vector', fieldValue: 'value6' },
|
||||
{ fieldName: 'field1', fieldType: 'semantic_text', fieldValue: 'value1' },
|
||||
{ fieldName: 'field2', fieldType: 'dense_vector', fieldValue: 'value2' },
|
||||
{ fieldName: 'field3', fieldType: 'sparse_vector', fieldValue: 'value3' },
|
||||
{ fieldName: 'field4', fieldType: 'semantic_text', fieldValue: 'value4' },
|
||||
{ fieldName: 'field5', fieldType: 'dense_vector', fieldValue: 'value5' },
|
||||
];
|
||||
const sortedFields = [
|
||||
{ fieldName: 'field1', fieldType: 'semantic_text', fieldValue: 'value1' },
|
||||
{ fieldName: 'field4', fieldType: 'semantic_text', fieldValue: 'value4' },
|
||||
{ fieldName: 'field2', fieldType: 'dense_vector', fieldValue: 'value2' },
|
||||
{ fieldName: 'field5', fieldType: 'dense_vector', fieldValue: 'value5' },
|
||||
{ fieldName: 'field3', fieldType: 'sparse_vector', fieldValue: 'value3' },
|
||||
{ fieldName: 'field6', fieldType: 'sparse_vector', fieldValue: 'value6' },
|
||||
];
|
||||
expect(reorderFieldsInImportance(fields)).toEqual(sortedFields);
|
||||
});
|
||||
|
||||
it('sorts fields if they are special fields', () => {
|
||||
const fields: FieldProps[] = [
|
||||
{ fieldName: 'field2', fieldType: 'dense_vector', fieldValue: 'value2' },
|
||||
{ fieldName: 'body_content', fieldType: 'sparse_vector', fieldValue: 'value3' },
|
||||
{ fieldName: 'headings', fieldType: 'text', fieldValue: 'value1' },
|
||||
{ fieldName: 'field4', fieldType: 'semantic_text', fieldValue: 'value4' },
|
||||
{ fieldName: 'field5', fieldType: 'dense_vector', fieldValue: 'value5' },
|
||||
{ fieldName: 'field6', fieldType: 'sparse_vector', fieldValue: 'value6' },
|
||||
];
|
||||
const sortedFields = [
|
||||
{ fieldName: 'headings', fieldType: 'text', fieldValue: 'value1' },
|
||||
{ fieldName: 'field4', fieldType: 'semantic_text', fieldValue: 'value4' },
|
||||
{ fieldName: 'field2', fieldType: 'dense_vector', fieldValue: 'value2' },
|
||||
{ fieldName: 'field5', fieldType: 'dense_vector', fieldValue: 'value5' },
|
||||
{ fieldName: 'body_content', fieldType: 'sparse_vector', fieldValue: 'value3' },
|
||||
{ fieldName: 'field6', fieldType: 'sparse_vector', fieldValue: 'value6' },
|
||||
];
|
||||
expect(reorderFieldsInImportance(fields)).toEqual(sortedFields);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import type {
|
|||
MappingProperty,
|
||||
SearchHit,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { MetaDataProps } from './result_types';
|
||||
import type { MetaDataProps, FieldProps } from './result_types';
|
||||
|
||||
const TITLE_KEYS = ['title', 'name'];
|
||||
|
||||
|
@ -44,10 +44,45 @@ export const resultMetaData = (result: SearchHit): MetaDataProps => ({
|
|||
score: result._score,
|
||||
});
|
||||
|
||||
const MAPPING_TYPE_ORDER = ['semantic_text', 'dense_vector', 'sparse_vector'];
|
||||
const SPECIAL_NAME_FIELDS = ['headings'];
|
||||
|
||||
/**
|
||||
* Reorders an array of fields based on their importance.
|
||||
*
|
||||
* The function sorts the fields by checking if their names are in the `SPECIAL_NAME_FIELDS` array first and then by
|
||||
* their mapping type (semantic_text, dense_vector, sparse_vector) if they are not in the `SPECIAL_NAME_FIELDS` array.
|
||||
*
|
||||
* @param fields - An array of field properties to be reordered.
|
||||
* @returns The reordered array of field properties.
|
||||
*/
|
||||
export const reorderFieldsInImportance = (fields: FieldProps[]) => {
|
||||
return fields.sort((a, b) => {
|
||||
const specialA = SPECIAL_NAME_FIELDS.indexOf(a.fieldName);
|
||||
const specialB = SPECIAL_NAME_FIELDS.indexOf(b.fieldName);
|
||||
|
||||
if (specialA !== -1 || specialB !== -1) {
|
||||
if (specialA === -1) return 1;
|
||||
if (specialB === -1) return -1;
|
||||
return specialA - specialB;
|
||||
}
|
||||
|
||||
const typeA = MAPPING_TYPE_ORDER.indexOf(a.fieldType);
|
||||
const typeB = MAPPING_TYPE_ORDER.indexOf(b.fieldType);
|
||||
const orderA = typeA === -1 ? MAPPING_TYPE_ORDER.length : typeA;
|
||||
const orderB = typeB === -1 ? MAPPING_TYPE_ORDER.length : typeB;
|
||||
|
||||
if (orderA === orderB) {
|
||||
return a.fieldName.localeCompare(b.fieldName);
|
||||
}
|
||||
return orderA - orderB;
|
||||
});
|
||||
};
|
||||
|
||||
export const resultToFieldFromMappingResponse = (
|
||||
result: SearchHit,
|
||||
mappings?: IndicesGetMappingResponse
|
||||
) => {
|
||||
): FieldProps[] => {
|
||||
if (mappings && mappings[result._index] && result._source && !Array.isArray(result._source)) {
|
||||
if (typeof result._source === 'object') {
|
||||
return Object.entries(result._source).map(([key, value]) => {
|
||||
|
@ -65,7 +100,7 @@ export const resultToFieldFromMappingResponse = (
|
|||
export const resultToFieldFromMappings = (
|
||||
result: SearchHit,
|
||||
mappings?: Record<string, MappingProperty>
|
||||
) => {
|
||||
): FieldProps[] => {
|
||||
if (mappings && result._source && !Array.isArray(result._source)) {
|
||||
return Object.entries(result._source).map(([key, value]) => {
|
||||
return {
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { MappingProperty, SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { IconType } from '@elastic/eui';
|
||||
|
||||
export interface ResultFieldProps {
|
||||
fieldName: string;
|
||||
fieldType?: string;
|
||||
fieldType: string;
|
||||
fieldValue: string;
|
||||
iconType?: IconType;
|
||||
isExpanded?: boolean;
|
||||
|
@ -24,3 +24,9 @@ export interface MetaDataProps {
|
|||
score?: SearchHit['_score'];
|
||||
showScore?: boolean;
|
||||
}
|
||||
|
||||
export interface FieldProps {
|
||||
fieldName: string;
|
||||
fieldType: Exclude<MappingProperty['type'], undefined>;
|
||||
fieldValue: string;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ const MetadataPopover: React.FC<MetaDataProps> = ({
|
|||
const metaDataIcon = (
|
||||
<EuiButtonIcon
|
||||
display="empty"
|
||||
size="xs"
|
||||
size="s"
|
||||
iconType="iInCircle"
|
||||
color="primary"
|
||||
data-test-subj="documentMetadataButton"
|
||||
|
@ -186,7 +186,7 @@ export const RichResultHeader: React.FC<Props> = ({
|
|||
<EuiFlexItem
|
||||
grow
|
||||
css={css`
|
||||
min-height: ${euiTheme.base * 3}px;
|
||||
min-height: ${euiTheme.base * 1}px;
|
||||
max-height: ${euiTheme.base * 8}px;
|
||||
`}
|
||||
>
|
||||
|
@ -204,12 +204,12 @@ export const RichResultHeader: React.FC<Props> = ({
|
|||
<EuiFlexItem>
|
||||
{onTitleClick ? (
|
||||
<EuiLink onClick={onTitleClick} color="text">
|
||||
<EuiTitle size="xs">
|
||||
<EuiTitle size="s">
|
||||
<h4>{title}</h4>
|
||||
</EuiTitle>
|
||||
</EuiLink>
|
||||
) : (
|
||||
<EuiTitle size="xs">
|
||||
<EuiTitle size="s">
|
||||
<h4>{title}</h4>
|
||||
</EuiTitle>
|
||||
)}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Result, resultMetaData, resultToField } from '@kbn/search-index-documen
|
|||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { reorderFieldsInImportance } from '@kbn/search-index-documents';
|
||||
import { RecentDocsActionMessage } from './recent_docs_action_message';
|
||||
import { useDeleteDocument } from '../../hooks/api/use_delete_document';
|
||||
|
||||
|
@ -32,7 +33,7 @@ export const DocumentList = ({ indexName, docs, mappingProperties }: DocumentLis
|
|||
return (
|
||||
<React.Fragment key={doc._id}>
|
||||
<Result
|
||||
fields={resultToField(doc, mappingProperties)}
|
||||
fields={reorderFieldsInImportance(resultToField(doc, mappingProperties))}
|
||||
metaData={resultMetaData(doc)}
|
||||
onDocumentDelete={() => {
|
||||
mutate({ id: doc._id! });
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue