From 303a2813da8873e917ee1a7560366d9b14d476a7 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 11:59:03 -0700 Subject: [PATCH 1/8] Set up promoted & hidden documents logic --- .../curations/curation/curation_logic.test.ts | 144 +++++++++++++++++- .../curations/curation/curation_logic.ts | 70 ++++++++- .../components/curations/utils.test.ts | 22 ++- .../app_search/components/curations/utils.ts | 11 ++ 4 files changed, 242 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 821dd21478027..8ea7126770a09 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -51,6 +51,10 @@ describe('CurationLogic', () => { queriesLoading: false, activeQuery: '', organicDocumentsLoading: false, + promotedIds: [], + promotedDocumentsLoading: false, + hiddenIds: [], + hiddenDocumentsLoading: false, }; beforeEach(() => { @@ -64,7 +68,7 @@ describe('CurationLogic', () => { describe('actions', () => { describe('onCurationLoad', () => { - it('should set curation, queries, activeQuery, & all loading states to false', () => { + it('should set curation, queries, activeQuery, promotedIds, hiddenIds, & all loading states to false', () => { mount(); CurationLogic.actions.onCurationLoad(MOCK_CURATION_RESPONSE); @@ -74,9 +78,13 @@ describe('CurationLogic', () => { curation: MOCK_CURATION_RESPONSE, queries: ['some search'], activeQuery: 'some search', + promotedIds: ['some-promoted-document'], + hiddenIds: ['some-hidden-document'], dataLoading: false, queriesLoading: false, organicDocumentsLoading: false, + promotedDocumentsLoading: false, + hiddenDocumentsLoading: false, }); }); @@ -95,6 +103,8 @@ describe('CurationLogic', () => { dataLoading: true, queriesLoading: true, organicDocumentsLoading: true, + promotedDocumentsLoading: true, + hiddenDocumentsLoading: true, }); CurationLogic.actions.onCurationError(); @@ -104,6 +114,8 @@ describe('CurationLogic', () => { dataLoading: false, queriesLoading: false, organicDocumentsLoading: false, + promotedDocumentsLoading: false, + hiddenDocumentsLoading: false, }); }); }); @@ -136,6 +148,104 @@ describe('CurationLogic', () => { }); }); }); + + describe('setPromotedIds', () => { + it('should set promotedIds state & promotedDocumentsLoading to true', () => { + mount(); + + CurationLogic.actions.setPromotedIds(['hello', 'world']); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: ['hello', 'world'], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('addPromotedId', () => { + it('should set promotedIds state & promotedDocumentsLoading to true', () => { + mount({ promotedIds: ['hello'] }); + + CurationLogic.actions.addPromotedId('world'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: ['hello', 'world'], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('removePromotedId', () => { + it('should set promotedIds state & promotedDocumentsLoading to true', () => { + mount({ promotedIds: ['hello', 'deleteme', 'world'] }); + + CurationLogic.actions.removePromotedId('deleteme'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: ['hello', 'world'], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('clearPromotedId', () => { + it('should reset promotedIds state & set promotedDocumentsLoading to true', () => { + mount({ promotedIds: ['hello', 'world'] }); + + CurationLogic.actions.clearPromotedIds(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + promotedIds: [], + promotedDocumentsLoading: true, + }); + }); + }); + + describe('addHiddenId', () => { + it('should set hiddenIds state & hiddenDocumentsLoading to true', () => { + mount({ hiddenIds: ['hello'] }); + + CurationLogic.actions.addHiddenId('world'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + hiddenIds: ['hello', 'world'], + hiddenDocumentsLoading: true, + }); + }); + }); + + describe('removeHiddenId', () => { + it('should set hiddenIds state & hiddenDocumentsLoading to true', () => { + mount({ hiddenIds: ['hello', 'deleteme', 'world'] }); + + CurationLogic.actions.removeHiddenId('deleteme'); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + hiddenIds: ['hello', 'world'], + hiddenDocumentsLoading: true, + }); + }); + }); + + describe('clearHiddenId', () => { + it('should reset hiddenIds state & set hiddenDocumentsLoading to true', () => { + mount({ hiddenIds: ['hello', 'world'] }); + + CurationLogic.actions.clearHiddenIds(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + hiddenIds: [], + hiddenDocumentsLoading: true, + }); + }); + }); }); describe('listeners', () => { @@ -187,6 +297,8 @@ describe('CurationLogic', () => { { queries: ['a', 'b', 'c'], activeQuery: 'b', + promotedIds: ['d', 'e', 'f'], + hiddenIds: ['g'], }, { curationId: 'cur-123456789' } ); @@ -199,7 +311,7 @@ describe('CurationLogic', () => { expect(http.put).toHaveBeenCalledWith( '/api/app_search/engines/some-engine/curations/cur-123456789', { - body: '{"queries":["a","b","c"],"query":"b","promoted":[],"hidden":[]}', // Uses state currently in CurationLogic + body: '{"queries":["a","b","c"],"query":"b","promoted":["d","e","f"],"hidden":["g"]}', // Uses state currently in CurationLogic } ); expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); @@ -249,6 +361,34 @@ describe('CurationLogic', () => { it('setActiveQuery', () => { CurationLogic.actions.setActiveQuery('test'); }); + + it('setPromotedIds', () => { + CurationLogic.actions.setPromotedIds(['test']); + }); + + it('addPromotedId', () => { + CurationLogic.actions.addPromotedId('test'); + }); + + it('removePromotedId', () => { + CurationLogic.actions.removePromotedId('test'); + }); + + it('clearPromotedIds', () => { + CurationLogic.actions.clearPromotedIds(); + }); + + it('addHiddenId', () => { + CurationLogic.actions.addHiddenId('test'); + }); + + it('removeHiddenId', () => { + CurationLogic.actions.removeHiddenId('test'); + }); + + it('clearHiddenIds', () => { + CurationLogic.actions.clearHiddenIds(); + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts index c3ee1aac57ace..45b5d9b6dd815 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts @@ -14,6 +14,7 @@ import { ENGINE_CURATIONS_PATH } from '../../../routes'; import { EngineLogic, generateEnginePath } from '../../engine'; import { Curation } from '../types'; +import { addDocument, removeDocument } from '../utils'; interface CurationValues { dataLoading: boolean; @@ -22,6 +23,10 @@ interface CurationValues { queriesLoading: boolean; activeQuery: string; organicDocumentsLoading: boolean; + promotedIds: string[]; + promotedDocumentsLoading: boolean; + hiddenIds: string[]; + hiddenDocumentsLoading: boolean; } interface CurationActions { @@ -31,6 +36,13 @@ interface CurationActions { onCurationError(): void; updateQueries(queries: Curation['queries']): { queries: Curation['queries'] }; setActiveQuery(query: string): { query: string }; + setPromotedIds(promotedIds: string[]): { promotedIds: string[] }; + addPromotedId(id: string): { id: string }; + removePromotedId(id: string): { id: string }; + clearPromotedIds(): void; + addHiddenId(id: string): { id: string }; + removeHiddenId(id: string): { id: string }; + clearHiddenIds(): void; } interface CurationProps { @@ -46,6 +58,13 @@ export const CurationLogic = kea ({ queries }), setActiveQuery: (query) => ({ query }), + setPromotedIds: (promotedIds) => ({ promotedIds }), + addPromotedId: (id) => ({ id }), + removePromotedId: (id) => ({ id }), + clearPromotedIds: true, + addHiddenId: (id) => ({ id }), + removeHiddenId: (id) => ({ id }), + clearHiddenIds: true, }), reducers: () => ({ dataLoading: [ @@ -99,6 +118,46 @@ export const CurationLogic = kea false, }, ], + promotedIds: [ + [], + { + onCurationLoad: (_, { curation }) => curation.promoted.map((document) => document.id), + setPromotedIds: (_, { promotedIds }) => promotedIds, + addPromotedId: (promotedIds, { id }) => addDocument(promotedIds, id), + removePromotedId: (promotedIds, { id }) => removeDocument(promotedIds, id), + clearPromotedIds: () => [], + }, + ], + promotedDocumentsLoading: [ + false, + { + setPromotedIds: () => true, + addPromotedId: () => true, + removePromotedId: () => true, + clearPromotedIds: () => true, + onCurationLoad: () => false, + onCurationError: () => false, + }, + ], + hiddenIds: [ + [], + { + onCurationLoad: (_, { curation }) => curation.hidden.map((document) => document.id), + addHiddenId: (hiddenIds, { id }) => addDocument(hiddenIds, id), + removeHiddenId: (hiddenIds, { id }) => removeDocument(hiddenIds, id), + clearHiddenIds: () => [], + }, + ], + hiddenDocumentsLoading: [ + false, + { + addHiddenId: () => true, + removeHiddenId: () => true, + clearHiddenIds: () => true, + onCurationLoad: () => false, + onCurationError: () => false, + }, + ], }), listeners: ({ actions, values, props }) => ({ loadCuration: async () => { @@ -131,8 +190,8 @@ export const CurationLogic = kea actions.updateCuration(), + setPromotedIds: () => actions.updateCuration(), + addPromotedId: () => actions.updateCuration(), + removePromotedId: () => actions.updateCuration(), + clearPromotedIds: () => actions.updateCuration(), + addHiddenId: () => actions.updateCuration(), + removeHiddenId: () => actions.updateCuration(), + clearHiddenIds: () => actions.updateCuration(), }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts index 435b76458db06..51618ed4e3741 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { convertToDate } from './utils'; +import { convertToDate, addDocument, removeDocument } from './utils'; describe('convertToDate', () => { it('converts the English-only server timestamps to a parseable Date', () => { @@ -16,3 +16,23 @@ describe('convertToDate', () => { expect(date.getFullYear()).toEqual(1970); }); }); + +describe('addDocument', () => { + it('adds a new document to the end of the document array without mutating the original array', () => { + const originalDocuments = ['hello']; + const newDocuments = addDocument(originalDocuments, 'world'); + + expect(newDocuments).toEqual(['hello', 'world']); + expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array + }); +}); + +describe('removeDocument', () => { + it('removes a specific document from the array without mutating the original array', () => { + const originalDocuments = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']; + const newDocuments = removeDocument(originalDocuments, 'dolor'); + + expect(newDocuments).toEqual(['lorem', 'ipsum', 'sit', 'amet']); + expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts index 2ef73e1de4e91..8af2636128304 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts @@ -14,3 +14,14 @@ export const convertToDate = (serverDateString: string): Date => { .replace('AM', ' AM'); return new Date(readableDateString); }; + +export const addDocument = (documentArray: string[], newDocument: string) => { + return [...documentArray, newDocument]; +}; + +export const removeDocument = (documentArray: string[], deletedDocument: string) => { + const newArray = [...documentArray]; + const indexToDelete = newArray.indexOf(deletedDocument); + newArray.splice(indexToDelete, 1); + return newArray; +}; From 1f2617c4ea221c60a1363b52b834e03040d5126b Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 12:23:50 -0700 Subject: [PATCH 2/8] Set up result utility for converting CurationResult to Result --- .../curations/curation/results/index.ts | 1 + .../curations/curation/results/utils.test.ts | 46 ++++++++++++++++ .../curations/curation/results/utils.ts | 52 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts index bbdb87bbe4fa9..0084b475c8d37 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts @@ -6,3 +6,4 @@ */ export { CurationResult } from './curation_result'; +export { convertToResultFormat } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts new file mode 100644 index 0000000000000..7bc05f34511a0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts @@ -0,0 +1,46 @@ +/* + * 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 { convertToResultFormat, convertIdToMeta } from './utils'; + +describe('convertToResultFormat', () => { + it('converts curation results to a format that the Result component can use', () => { + expect( + convertToResultFormat({ + id: 'some-id', + someField: 'some flat string', + anotherField: '123456', + }) + ).toEqual({ + _meta: { + id: 'some-id', + }, + id: { + raw: 'some-id', + snippet: null, + }, + someField: { + raw: 'some flat string', + snippet: null, + }, + anotherField: { + raw: '123456', + snippet: null, + }, + }); + }); +}); + +describe('convertIdToMeta', () => { + it('creates an approximate _meta object based on the curation result ID', () => { + expect(convertIdToMeta('some-id')).toEqual({ id: 'some-id' }); + expect(convertIdToMeta('some-engine|some-id')).toEqual({ + id: 'some-id', + engine: 'some-engine', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts new file mode 100644 index 0000000000000..b5a5bf1b5a90a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts @@ -0,0 +1,52 @@ +/* + * 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 { Result } from '../../../result/types'; +import { CurationResult } from '../../types'; + +/** + * The `promoted` and `hidden` keys from the internal curations endpoints + * currently return a document data structure that our Result component can't + * correctly parse - we need to attempt to naively transform the data in order + * to display it in a Result + * + * TODO: Ideally someday we can update our internal curations endpoint to return + * the same Result-ready data structure that the `organic` endpoint uses, and + * remove this file when that happens + */ + +export const convertToResultFormat = (document: CurationResult): Result => { + const result = {} as Result; + + // Convert `key: 'value'` into `key: { raw: 'value' }` + Object.entries(document).forEach(([key, value]) => { + result[key] = { + raw: value, + snippet: null, // Don't try to provide a snippet, we can't really guesstimate it + }; + }); + + // Add the _meta obj needed by Result + result._meta = convertIdToMeta(document.id); + + return result; +}; + +export const convertIdToMeta = (id: string): Result['_meta'] => { + const splitId = id.split('|'); + const isMetaEngine = splitId.length > 1; + + return isMetaEngine + ? { + engine: splitId[0], + id: splitId[1], + } + : ({ id } as Result['_meta']); + // Note: We're casting this as _meta even though `engine` is missing, + // since for source engines the engine shouldn't matter / be displayed, + // but if needed we could likely populate this from EngineLogic.values +}; From df49b74d2c7c86e099f29a85ba84a86cd684e10e Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 12:31:53 -0700 Subject: [PATCH 3/8] Set up AddResultButton in documents sections - not hooked up to anything right now, but will be in the next PR --- .../results/add_result_button.test.tsx | 31 +++++++++++++++++++ .../curation/results/add_result_button.tsx | 21 +++++++++++++ .../curations/curation/results/index.ts | 1 + 3 files changed, 53 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx new file mode 100644 index 0000000000000..78f5325ee567b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx @@ -0,0 +1,31 @@ +/* + * 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 { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiButton } from '@elastic/eui'; + +import { AddResultButton } from './'; + +describe('AddResultButton', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('renders', () => { + expect(wrapper.find(EuiButton)).toHaveLength(1); + }); + + it('opens the add result flyout on click', () => { + wrapper.find(EuiButton).simulate('click'); + // TODO: assert on logic action + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx new file mode 100644 index 0000000000000..9bbc62ae51a0b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx @@ -0,0 +1,21 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const AddResultButton: React.FC = () => { + return ( + {} /* TODO */} iconType="plusInCircle" size="s" fill> + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', { + defaultMessage: 'Add result manually', + })} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts index 0084b475c8d37..3c6339f0c1942 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts @@ -5,5 +5,6 @@ * 2.0. */ +export { AddResultButton } from './add_result_button'; export { CurationResult } from './curation_result'; export { convertToResultFormat } from './utils'; From 8788bb3dcdfbebe8cb312b15389fae796795e78c Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 12:59:43 -0700 Subject: [PATCH 4/8] Add HiddenDocuments section --- .../components/curations/constants.ts | 14 +++ .../curations/curation/curation.tsx | 5 +- .../documents/hidden_documents.test.tsx | 83 ++++++++++++++++ .../curation/documents/hidden_documents.tsx | 99 +++++++++++++++++++ .../curations/curation/documents/index.ts | 1 + 5 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts index 8d70f1c049b1f..7278a5a732414 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts @@ -37,3 +37,17 @@ export const RESULT_ACTIONS_DIRECTIONS = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription', { defaultMessage: 'Promote results by clicking the star, hide them by clicking the eye.' } ); +export const HIDE_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel', { + defaultMessage: 'Hide this result', + }), + iconType: 'eyeClosed', + iconColor: 'danger', +}; +export const SHOW_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel', { + defaultMessage: 'Show this result', + }), + iconType: 'eye', + iconColor: 'primary', +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx index 221c2419b7448..1529c35c8b1a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx @@ -20,7 +20,7 @@ import { Loading } from '../../../../shared/loading'; import { MANAGE_CURATION_TITLE } from '../constants'; import { CurationLogic } from './curation_logic'; -import { OrganicDocuments } from './documents'; +import { OrganicDocuments, HiddenDocuments } from './documents'; import { ActiveQuerySelect, ManageQueriesModal } from './queries'; interface Props { @@ -61,7 +61,8 @@ export const Curation: React.FC = ({ curationsBreadcrumb }) => { {/* TODO: PromotedDocuments section */} - {/* TODO: HiddenDocuments section */} + + {/* TODO: AddResult flyout */} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx new file mode 100644 index 0000000000000..7ffa45c285320 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui'; + +import { DataPanel } from '../../../data_panel'; +import { CurationResult } from '../results'; + +import { HiddenDocuments } from './'; + +describe('HiddenDocuments', () => { + const values = { + curation: { + hidden: [ + { id: 'mock-document-1' }, + { id: 'mock-document-2' }, + { id: 'mock-document-3' }, + { id: 'mock-document-4' }, + { id: 'mock-document-5' }, + ], + }, + hiddenDocumentsLoading: false, + }; + const actions = { + removeHiddenId: jest.fn(), + clearHiddenIds: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders a list of hidden documents', () => { + const wrapper = shallow(); + + expect(wrapper.find(CurationResult)).toHaveLength(5); + }); + + it('renders an empty state & hides the panel actions when empty', () => { + setMockValues({ ...values, curation: { hidden: [] } }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find(DataPanel).prop('action')).toBe(false); + }); + + it('renders a loading state', () => { + setMockValues({ ...values, hiddenDocumentsLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true); + }); + + describe('actions', () => { + it('renders results with an action button that un-hides the result', () => { + const wrapper = shallow(); + const result = wrapper.find(CurationResult).last(); + result.prop('actions')[0].onClick(); + + expect(actions.removeHiddenId).toHaveBeenCalledWith('mock-document-5'); + }); + + it('renders a restore all button that un-hides all hidden results', () => { + const wrapper = shallow(); + const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); + + panelActions.find(EuiButtonEmpty).simulate('click'); + expect(actions.clearHiddenIds).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx new file mode 100644 index 0000000000000..feb0bcf9a389b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx @@ -0,0 +1,99 @@ +/* + * 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 { useValues, useActions } from 'kea'; + +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DataPanel } from '../../../data_panel'; + +import { SHOW_DOCUMENT_ACTION } from '../../constants'; +import { CurationLogic } from '../curation_logic'; +import { AddResultButton, CurationResult, convertToResultFormat } from '../results'; + +export const HiddenDocuments: React.FC = () => { + const { clearHiddenIds, removeHiddenId } = useActions(CurationLogic); + const { curation, hiddenDocumentsLoading } = useValues(CurationLogic); + + const documents = curation.hidden; + const hasDocuments = documents.length > 0; + + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title', + { defaultMessage: 'Hidden documents' } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.description', + { defaultMessage: 'Hidden documents will not appear in organic results.' } + )} + action={ + hasDocuments && ( + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel', + { defaultMessage: 'Restore all' } + )} + + + + ) + } + isLoading={hiddenDocumentsLoading} + > + {hasDocuments ? ( + documents.map((document) => ( + removeHiddenId(document.id), + }, + ]} + /> + )) + ) : ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle', + { defaultMessage: 'No documents are being hidden for this query' } + )} + + } + body={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription', + { + defaultMessage: + 'Hide documents by clicking the eye icon on the organic results above, or search and hide a result manually.', + } + )} + actions={} + /> + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts index fdaadeb5ced95..a0d7edcdf33bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts @@ -6,3 +6,4 @@ */ export { OrganicDocuments } from './organic_documents'; +export { HiddenDocuments } from './hidden_documents'; From 942cbe86f2aec696c08481c4006d4fa794e47a9e Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 13:53:27 -0700 Subject: [PATCH 5/8] Add PromotedDocuments section w/ draggable results --- .../components/curations/constants.ts | 14 ++ .../curations/curation/curation.tsx | 5 +- .../curations/curation/documents/index.ts | 1 + .../documents/promoted_documents.test.tsx | 116 ++++++++++++++++ .../curation/documents/promoted_documents.tsx | 124 ++++++++++++++++++ 5 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts index 7278a5a732414..f70143ccd45a3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts @@ -37,6 +37,20 @@ export const RESULT_ACTIONS_DIRECTIONS = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription', { defaultMessage: 'Promote results by clicking the star, hide them by clicking the eye.' } ); +export const PROMOTE_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel', { + defaultMessage: 'Promote this result', + }), + iconType: 'starPlusEmpty', + iconColor: 'primary', +}; +export const DEMOTE_DOCUMENT_ACTION = { + title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel', { + defaultMessage: 'Demote this result', + }), + iconType: 'starMinusFilled', + iconColor: 'primary', +}; export const HIDE_DOCUMENT_ACTION = { title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel', { defaultMessage: 'Hide this result', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx index 1529c35c8b1a1..a2d867a4cec05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx @@ -20,7 +20,7 @@ import { Loading } from '../../../../shared/loading'; import { MANAGE_CURATION_TITLE } from '../constants'; import { CurationLogic } from './curation_logic'; -import { OrganicDocuments, HiddenDocuments } from './documents'; +import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents'; import { ActiveQuerySelect, ManageQueriesModal } from './queries'; interface Props { @@ -59,7 +59,8 @@ export const Curation: React.FC = ({ curationsBreadcrumb }) => { - {/* TODO: PromotedDocuments section */} + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts index a0d7edcdf33bb..3548f6f298069 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts @@ -5,5 +5,6 @@ * 2.0. */ +export { PromotedDocuments } from './promoted_documents'; export { OrganicDocuments } from './organic_documents'; export { HiddenDocuments } from './hidden_documents'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx new file mode 100644 index 0000000000000..7240a443b76e9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx @@ -0,0 +1,116 @@ +/* + * 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 { setMockValues, setMockActions } from '../../../../../__mocks__'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiDragDropContext, EuiDraggable, EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui'; + +import { DataPanel } from '../../../data_panel'; +import { CurationResult } from '../results'; + +import { PromotedDocuments } from './'; + +describe('PromotedDocuments', () => { + const values = { + curation: { + promoted: [ + { id: 'mock-document-1' }, + { id: 'mock-document-2' }, + { id: 'mock-document-3' }, + { id: 'mock-document-4' }, + ], + }, + promotedIds: ['mock-document-1', 'mock-document-2', 'mock-document-3', 'mock-document-4'], + promotedDocumentsLoading: false, + }; + const actions = { + setPromotedIds: jest.fn(), + clearPromotedIds: jest.fn(), + removePromotedId: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + const getDraggableChildren = (draggableWrapper: any) => { + return draggableWrapper.renderProp('children')({}, {}, {}); + }; + + it('renders a list of draggable promoted documents', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiDraggable)).toHaveLength(4); + + wrapper.find(EuiDraggable).forEach((draggableWrapper) => { + expect(getDraggableChildren(draggableWrapper).find(CurationResult).exists()).toBe(true); + }); + }); + + it('renders an empty state & hides the panel actions when empty', () => { + setMockValues({ ...values, curation: { promoted: [] } }); + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + expect(wrapper.find(DataPanel).prop('action')).toBe(false); + }); + + it('renders a loading state', () => { + setMockValues({ ...values, promotedDocumentsLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true); + }); + + describe('actions', () => { + it('renders results with an action button that demotes the result', () => { + const wrapper = shallow(); + const result = getDraggableChildren(wrapper.find(EuiDraggable).last()); + result.prop('actions')[0].onClick(); + + expect(actions.removePromotedId).toHaveBeenCalledWith('mock-document-4'); + }); + + it('renders a demote all button that demotes all hidden results', () => { + const wrapper = shallow(); + const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); + + panelActions.find(EuiButtonEmpty).simulate('click'); + expect(actions.clearPromotedIds).toHaveBeenCalled(); + }); + + describe('draggging', () => { + it('calls setPromotedIds with the reordered list when users are done dragging', () => { + const wrapper = shallow(); + wrapper.find(EuiDragDropContext).simulate('dragEnd', { + source: { index: 3 }, + destination: { index: 0 }, + }); + + expect(actions.setPromotedIds).toHaveBeenCalledWith([ + 'mock-document-4', + 'mock-document-1', + 'mock-document-2', + 'mock-document-3', + ]); + }); + + it('does not error if source/destination are unavailable on drag end', () => { + const wrapper = shallow(); + wrapper.find(EuiDragDropContext).simulate('dragEnd', {}); + + expect(actions.setPromotedIds).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx new file mode 100644 index 0000000000000..877ff0d3cd40f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx @@ -0,0 +1,124 @@ +/* + * 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 { useValues, useActions } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiEmptyPrompt, + EuiButtonEmpty, + EuiDragDropContext, + DropResult, + EuiDroppable, + EuiDraggable, + euiDragDropReorder, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DataPanel } from '../../../data_panel'; + +import { DEMOTE_DOCUMENT_ACTION } from '../../constants'; +import { CurationLogic } from '../curation_logic'; +import { AddResultButton, CurationResult, convertToResultFormat } from '../results'; + +export const PromotedDocuments: React.FC = () => { + const { curation, promotedIds, promotedDocumentsLoading } = useValues(CurationLogic); + const documents = curation.promoted; + const hasDocuments = documents.length > 0; + + const { setPromotedIds, clearPromotedIds, removePromotedId } = useActions(CurationLogic); + const reorderPromotedIds = ({ source, destination }: DropResult) => { + if (source && destination) { + const reorderedIds = euiDragDropReorder(promotedIds, source.index, destination.index); + setPromotedIds(reorderedIds); + } + }; + + return ( + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title', + { defaultMessage: 'Promoted documents' } + )} + + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description', + { + defaultMessage: + 'Promoted results appear before organic results. Documents can be re-ordered.', + } + )} + action={ + hasDocuments && ( + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel', + { defaultMessage: 'Demote all' } + )} + + + + ) + } + isLoading={promotedDocumentsLoading} + > + {hasDocuments ? ( + + + {documents.map((document, i: number) => ( + + {(provided) => ( + removePromotedId(document.id), + }, + ]} + dragHandleProps={provided.dragHandleProps} + /> + )} + + ))} + + + ) : ( + } + /> + )} + + ); +}; From 8ca1efe26cfb053fdeeaa71fefb1a82641e2ea9a Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 14:01:15 -0700 Subject: [PATCH 6/8] Update OrganicDocuments results with promote/hide actions --- .../documents/organic_documents.test.tsx | 25 ++++++++++++++++++- .../curation/documents/organic_documents.tsx | 20 ++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx index fd26cb1acf7a6..2a83ecfcada44 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { setMockValues } from '../../../../../__mocks__'; +import { setMockValues, setMockActions } from '../../../../../__mocks__'; import React from 'react'; @@ -31,10 +31,15 @@ describe('OrganicDocuments', () => { activeQuery: 'world', organicDocumentsLoading: false, }; + const actions = { + addPromotedId: jest.fn(), + addHiddenId: jest.fn(), + }; beforeEach(() => { jest.clearAllMocks(); setMockValues(values); + setMockActions(actions); }); it('renders a list of organic results', () => { @@ -64,4 +69,22 @@ describe('OrganicDocuments', () => { expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); }); + + describe('actions', () => { + it('renders results with an action button that promotes the result', () => { + const wrapper = shallow(); + const result = wrapper.find(CurationResult).first(); + result.prop('actions')[1].onClick(); + + expect(actions.addPromotedId).toHaveBeenCalledWith('mock-document-1'); + }); + + it('renders results with an action button that hides the result', () => { + const wrapper = shallow(); + const result = wrapper.find(CurationResult).last(); + result.prop('actions')[0].onClick(); + + expect(actions.addHiddenId).toHaveBeenCalledWith('mock-document-3'); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx index 3aa65a14e7a2f..a3a761feefcd2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { useValues } from 'kea'; +import { useValues, useActions } from 'kea'; import { EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -15,11 +15,16 @@ import { i18n } from '@kbn/i18n'; import { DataPanel } from '../../../data_panel'; import { Result } from '../../../result/types'; -import { RESULT_ACTIONS_DIRECTIONS } from '../../constants'; +import { + RESULT_ACTIONS_DIRECTIONS, + PROMOTE_DOCUMENT_ACTION, + HIDE_DOCUMENT_ACTION, +} from '../../constants'; import { CurationLogic } from '../curation_logic'; import { CurationResult } from '../results'; export const OrganicDocuments: React.FC = () => { + const { addPromotedId, addHiddenId } = useActions(CurationLogic); const { curation, activeQuery, organicDocumentsLoading } = useValues(CurationLogic); const documents = curation.organic; @@ -48,7 +53,16 @@ export const OrganicDocuments: React.FC = () => { addHiddenId(document.id.raw), + }, + { + ...PROMOTE_DOCUMENT_ACTION, + onClick: () => addPromotedId(document.id.raw), + }, + ]} /> )) ) : organicDocumentsLoading ? ( From f2b6446adda001a4b628f7fb62d3856e3479178d Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Tue, 16 Mar 2021 15:08:29 -0700 Subject: [PATCH 7/8] Add the Restore Defaults button+logic --- .../components/curations/constants.ts | 7 ++++ .../curations/curation/curation.test.tsx | 32 ++++++++++++++++++- .../curations/curation/curation.tsx | 20 +++++++++--- .../curations/curation/curation_logic.test.ts | 17 ++++++++++ .../curations/curation/curation_logic.ts | 7 ++++ 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts index f70143ccd45a3..57af8cada9890 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts @@ -32,6 +32,13 @@ export const SUCCESS_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage', { defaultMessage: 'Successfully removed curation.' } ); +export const RESTORE_CONFIRMATION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation', + { + defaultMessage: + 'Are you sure you want to clear your changes and return to your default results?', + } +); export const RESULT_ACTIONS_DIRECTIONS = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx index 748b5670e1d1d..bbf1b95e251da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx @@ -12,7 +12,7 @@ import { setMockActions, setMockValues, rerender } from '../../../../__mocks__'; import React from 'react'; import { useParams } from 'react-router-dom'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { EuiPageHeader } from '@elastic/eui'; @@ -34,6 +34,7 @@ describe('Curation', () => { }; const actions = { loadCuration: jest.fn(), + resetCuration: jest.fn(), }; beforeEach(() => { @@ -75,4 +76,33 @@ describe('Curation', () => { rerender(wrapper); expect(actions.loadCuration).toHaveBeenCalledTimes(2); }); + + describe('restore defaults button', () => { + let restoreDefaultsButton: ShallowWrapper; + let confirmSpy: jest.SpyInstance; + + beforeAll(() => { + const wrapper = shallow(); + const headerActions = wrapper.find(EuiPageHeader).prop('rightSideItems'); + restoreDefaultsButton = shallow(headerActions![0] as React.ReactElement); + + confirmSpy = jest.spyOn(window, 'confirm'); + }); + + afterAll(() => { + confirmSpy.mockRestore(); + }); + + it('resets the curation upon user confirmation', () => { + confirmSpy.mockReturnValueOnce(true); + restoreDefaultsButton.simulate('click'); + expect(actions.resetCuration).toHaveBeenCalled(); + }); + + it('does not reset the curation if the user cancels', () => { + confirmSpy.mockReturnValueOnce(false); + restoreDefaultsButton.simulate('click'); + expect(actions.resetCuration).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx index a2d867a4cec05..85e91dabc6108 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx @@ -10,14 +10,15 @@ import { useParams } from 'react-router-dom'; import { useValues, useActions } from 'kea'; -import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FlashMessages } from '../../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs'; import { Loading } from '../../../../shared/loading'; -import { MANAGE_CURATION_TITLE } from '../constants'; +import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants'; import { CurationLogic } from './curation_logic'; import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents'; @@ -29,7 +30,7 @@ interface Props { export const Curation: React.FC = ({ curationsBreadcrumb }) => { const { curationId } = useParams() as { curationId: string }; - const { loadCuration } = useActions(CurationLogic({ curationId })); + const { loadCuration, resetCuration } = useActions(CurationLogic({ curationId })); const { dataLoading, queries } = useValues(CurationLogic({ curationId })); useEffect(() => { @@ -43,7 +44,18 @@ export const Curation: React.FC = ({ curationsBreadcrumb }) => { { + if (window.confirm(RESTORE_CONFIRMATION)) resetCuration(); + }} + > + {i18n.translate('xpack.enterpriseSearch.appSearch.actions.restoreDefaults', { + defaultMessage: 'Restore defaults', + })} + , + ]} responsive={false} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 8ea7126770a09..17f7cd7cd154e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -246,6 +246,23 @@ describe('CurationLogic', () => { }); }); }); + + describe('resetCuration', () => { + it('should clear promotedIds & hiddenIds & set dataLoading to true', () => { + mount({ promotedIds: ['hello'], hiddenIds: ['world'] }); + + CurationLogic.actions.resetCuration(); + + expect(CurationLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + promotedIds: [], + promotedDocumentsLoading: true, + hiddenIds: [], + hiddenDocumentsLoading: true, + }); + }); + }); }); describe('listeners', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts index 45b5d9b6dd815..9fa2d353b4ef2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts @@ -43,6 +43,7 @@ interface CurationActions { addHiddenId(id: string): { id: string }; removeHiddenId(id: string): { id: string }; clearHiddenIds(): void; + resetCuration(): void; } interface CurationProps { @@ -65,12 +66,14 @@ export const CurationLogic = kea ({ id }), removeHiddenId: (id) => ({ id }), clearHiddenIds: true, + resetCuration: true, }), reducers: () => ({ dataLoading: [ true, { loadCuration: () => true, + resetCuration: () => true, onCurationLoad: () => false, onCurationError: () => false, }, @@ -215,5 +218,9 @@ export const CurationLogic = kea actions.updateCuration(), removeHiddenId: () => actions.updateCuration(), clearHiddenIds: () => actions.updateCuration(), + resetCuration: () => { + actions.clearPromotedIds(); + actions.clearHiddenIds(); + }, }), }); From b0ae5fe80bc72f37e7d25f3d1e53c76c93ecf2ea Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 17 Mar 2021 14:26:00 -0700 Subject: [PATCH 8/8] PR feedback: key ID --- .../curations/curation/documents/promoted_documents.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx index 877ff0d3cd40f..18f7a7bd8b960 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx @@ -85,7 +85,7 @@ export const PromotedDocuments: React.FC = () => { {documents.map((document, i: number) => (