From df2eb59960ac828c3c733300c2a5593c69b2928a Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 18 Dec 2020 17:01:05 -0500 Subject: [PATCH] [App Search] Updates to results on the documents view (#86181) --- .../build_search_ui_config.test.ts | 38 ++++++++++ .../build_search_ui_config.ts | 34 +++++++++ .../search_experience/search_experience.tsx | 11 +-- .../search_experience_content.test.tsx | 11 ++- .../search_experience_content.tsx | 4 +- .../views/result_view.test.tsx | 13 +++- .../search_experience/views/result_view.tsx | 10 ++- .../app_search/components/library/library.tsx | 37 +++++++++- .../app_search/components/result/result.scss | 1 + .../components/result/result.test.tsx | 36 +++++++++ .../app_search/components/result/result.tsx | 73 +++++++++++++------ .../components/result/result_field_value.scss | 5 -- .../result/result_field_value.test.tsx | 4 +- .../components/result/result_field_value.tsx | 5 +- .../public/applications/app_search/routes.ts | 3 + 15 files changed, 235 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts new file mode 100644 index 0000000000000..dd52f6b8227ba --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SchemaTypes } from '../../../../shared/types'; + +import { buildSearchUIConfig } from './build_search_ui_config'; + +describe('buildSearchUIConfig', () => { + it('builds a configuration object for Search UI', () => { + const connector = {}; + const schema = { + foo: 'text' as SchemaTypes, + bar: 'number' as SchemaTypes, + }; + + const config = buildSearchUIConfig(connector, schema); + expect(config.apiConnector).toEqual(connector); + expect(config.searchQuery.result_fields).toEqual({ + bar: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + foo: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts new file mode 100644 index 0000000000000..533adbaf5bab9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Schema } from '../../../../shared/types'; + +export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => { + return { + alwaysSearchOnInitialLoad: true, + apiConnector, + trackUrlState: false, + initialState: { + sortDirection: 'desc', + sortField: 'id', + }, + searchQuery: { + result_fields: Object.keys(schema || {}).reduce( + (acc: { [key: string]: object }, key: string) => { + acc[key] = { + snippet: { + size: 300, + fallback: true, + }, + raw: {}, + }; + return acc; + }, + {} + ), + }, + }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx index 49cc573b686bc..1501efc589fc0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx @@ -20,6 +20,7 @@ import { externalUrl } from '../../../../shared/enterprise_search_url'; import { SearchBoxView, SortingView } from './views'; import { SearchExperienceContent } from './search_experience_content'; +import { buildSearchUIConfig } from './build_search_ui_config'; const DEFAULT_SORT_OPTIONS = [ { @@ -52,15 +53,7 @@ export const SearchExperience: React.FC = () => { searchKey: engine.apiKey, }); - const searchProviderConfig = { - alwaysSearchOnInitialLoad: true, - apiConnector: connector, - trackUrlState: false, - initialState: { - sortDirection: 'desc', - sortField: 'id', - }, - }; + const searchProviderConfig = buildSearchUIConfig(connector, engine.schema || {}); return (
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx index 455e237848a4b..a46ec560a13e0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx @@ -15,6 +15,7 @@ import { Results } from '@elastic/react-search-ui'; import { ResultView } from './views'; import { Pagination } from './pagination'; +import { SchemaTypes } from '../../../../shared/types'; import { SearchExperienceContent } from './search_experience_content'; describe('SearchExperienceContent', () => { @@ -27,6 +28,11 @@ describe('SearchExperienceContent', () => { engineName: 'engine1', isMetaEngine: false, myRole: { canManageEngineDocuments: true }, + engine: { + schema: { + title: 'string' as SchemaTypes, + }, + }, }; beforeEach(() => { @@ -40,7 +46,7 @@ describe('SearchExperienceContent', () => { expect(wrapper.isEmptyRender()).toBe(false); }); - it('passes engineName to the result view', () => { + it('passes engineName and schema to the result view', () => { const props = { result: { id: { @@ -56,6 +62,9 @@ describe('SearchExperienceContent', () => { raw: 'bar', }, }, + schemaForTypeHighlights: { + title: 'string' as SchemaTypes, + }, }; const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx index 9194a3a1db5e4..55a8377261dd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx @@ -25,7 +25,7 @@ export const SearchExperienceContent: React.FC = () => { const { resultSearchTerm, totalResults, wasSearched } = useSearchContextState(); const { myRole } = useValues(AppLogic); - const { isMetaEngine } = useValues(EngineLogic); + const { isMetaEngine, engine } = useValues(EngineLogic); if (!wasSearched) return null; @@ -44,7 +44,7 @@ export const SearchExperienceContent: React.FC = () => { { - return ; + return ; }} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx index 049a3ad1bed66..91334f312623d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ResultView } from '.'; +import { SchemaTypes } from '../../../../../shared/types'; import { Result } from '../../../result/result'; describe('ResultView', () => { @@ -27,8 +28,16 @@ describe('ResultView', () => { }, }; + const schema = { + title: 'string' as SchemaTypes, + }; + it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(Result).exists()).toBe(true); + const wrapper = shallow(); + expect(wrapper.find(Result).props()).toEqual({ + result, + shouldLinkToDetailPage: true, + schemaForTypeHighlights: schema, + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx index 52b845a1aee2d..543c63b334940 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx @@ -7,16 +7,22 @@ import React from 'react'; import { Result as ResultType } from '../../../result/types'; +import { Schema } from '../../../../../shared/types'; import { Result } from '../../../result/result'; export interface Props { result: ResultType; + schemaForTypeHighlights?: Schema; } -export const ResultView: React.FC = ({ result }) => { +export const ResultView: React.FC = ({ result, schemaForTypeHighlights }) => { return (
  • - +
  • ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx index 66c0cc165fc05..1b222cfaacf7c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx @@ -15,6 +15,7 @@ import { import React from 'react'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { Schema } from '../../../shared/types'; import { Result } from '../result/result'; export const Library: React.FC = () => { @@ -35,12 +36,18 @@ export const Library: React.FC = () => { description: { raw: 'A description', }, - states: { - raw: ['Pennsylvania', 'Ohio'], + date_established: { + raw: '1968-10-02T05:00:00Z', + }, + location: { + raw: '37.3,-113.05', }, visitors: { raw: 1000, }, + states: { + raw: ['Pennsylvania', 'Ohio'], + }, size: { raw: 200, }, @@ -50,6 +57,17 @@ export const Library: React.FC = () => { }, }; + const schema: Schema = { + title: 'text', + description: 'text', + date_established: 'date', + location: 'geolocation', + states: 'text', + visitors: 'number', + size: 'number', + length: 'number', + }; + return ( <> @@ -170,6 +188,21 @@ export const Library: React.FC = () => { }, }} /> + + + +

    With a link

    +
    + + + + + + +

    With field value type highlights

    +
    + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss index ed8ce512a2eb8..8342061ee00c3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss @@ -5,6 +5,7 @@ width: 100%; padding: $euiSize; overflow: hidden; + color: $euiTextColor; } &__hiddenFieldsIndicator { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index ade26551039fa..5b598a0b8565e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -11,6 +11,9 @@ import { EuiPanel } from '@elastic/eui'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { SchemaTypes } from '../../../shared/types'; + import { Result } from './result'; describe('Result', () => { @@ -37,6 +40,12 @@ describe('Result', () => { }, }; + const schema = { + title: 'text' as SchemaTypes, + description: 'text' as SchemaTypes, + length: 'number' as SchemaTypes, + }; + it('renders', () => { const wrapper = shallow(); expect(wrapper.find(EuiPanel).exists()).toBe(true); @@ -62,6 +71,33 @@ describe('Result', () => { }); }); + describe('document detail link', () => { + it('will render a link if shouldLinkToDetailPage is true', () => { + const wrapper = shallow(); + expect(wrapper.find(ReactRouterHelper).prop('to')).toEqual('/engines/my-engine/documents/1'); + expect(wrapper.find('article.appSearchResult__content').exists()).toBe(false); + expect(wrapper.find('a.appSearchResult__content').exists()).toBe(true); + }); + + it('will not render a link if shouldLinkToDetailPage is not set', () => { + const wrapper = shallow(); + expect(wrapper.find(ReactRouterHelper).exists()).toBe(false); + expect(wrapper.find('article.appSearchResult__content').exists()).toBe(true); + expect(wrapper.find('a.appSearchResult__content').exists()).toBe(false); + }); + }); + + it('will render field details with type highlights if schemaForTypeHighlights has been provided', () => { + const wrapper = shallow( + + ); + expect(wrapper.find(ResultField).map((rf) => rf.prop('type'))).toEqual([ + 'text', + 'text', + 'number', + ]); + }); + describe('when there are more than 5 fields', () => { const propsWithMoreFields = { result: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 4f343e64b12ae..11415f5512380 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -14,15 +14,25 @@ import { i18n } from '@kbn/i18n'; import { FieldValue, Result as ResultType } from './types'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; +import { getDocumentDetailRoute } from '../../routes'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { Schema } from '../../../shared/types'; interface Props { result: ResultType; showScore?: boolean; + shouldLinkToDetailPage?: boolean; + schemaForTypeHighlights?: Schema; } const RESULT_CUTOFF = 5; -export const Result: React.FC = ({ result, showScore }) => { +export const Result: React.FC = ({ + result, + showScore = false, + shouldLinkToDetailPage = false, + schemaForTypeHighlights, +}) => { const [isOpen, setIsOpen] = useState(false); const ID = 'id'; @@ -33,6 +43,19 @@ export const Result: React.FC = ({ result, showScore }) => { [result] ); const numResults = resultFields.length; + const typeForField = (fieldName: string) => { + if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; + }; + + const conditionallyLinkedArticle = (children: React.ReactNode) => { + return shouldLinkToDetailPage ? ( + + {children} + + ) : ( +
    {children}
    + ); + }; return ( = ({ result, showScore }) => { defaultMessage: 'View document details', })} > -
    - -
    - {resultFields - .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) - .map(([field, value]: [string, FieldValue]) => ( - - ))} -
    - {numResults > RESULT_CUTOFF && !isOpen && ( -
    - {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { - defaultMessage: '{numberOfAdditionalFields} more fields', - values: { - numberOfAdditionalFields: numResults - RESULT_CUTOFF, - }, - })} -
    - )} -
    + {conditionallyLinkedArticle( + <> + +
    + {resultFields + .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) + .map(([field, value]: [string, FieldValue]) => ( + + ))} +
    + {numResults > RESULT_CUTOFF && !isOpen && ( +
    + {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { + defaultMessage: '{numberOfAdditionalFields} more fields', + values: { + numberOfAdditionalFields: numResults - RESULT_CUTOFF, + }, + })} +
    + )} + + )} {numResults > RESULT_CUTOFF && (