diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts index 5a4ac2b0549df..96e300e0d0520 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts @@ -13,6 +13,7 @@ export interface CurationSuggestion { updated_at: string; promoted: string[]; status: 'pending' | 'applied' | 'automated' | 'rejected' | 'disabled'; + curation_id?: string; override_curation_id?: string; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx index b61355d0b8555..5a21bfcb38843 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx @@ -73,7 +73,7 @@ export const CurationResultPanel: React.FC = ({ variant, results }) => { > {results.length > 0 ? ( results.map((result) => ( - + { }, }, ], + curation: { + promoted: [ + { + id: '4', + foo: 'foo', + }, + ], + }, isMetaEngine: true, engine: { schema: {}, @@ -88,6 +96,27 @@ describe('CurationSuggestion', () => { expect(actions.loadSuggestion).toHaveBeenCalled(); }); + it('shows existing promoted documents', () => { + const wrapper = shallow(); + const suggestedResultsPanel = wrapper.find(CurationResultPanel).at(0); + // gets populated from 'curation' in state, and converted to results format (i.e, has raw properties, etc.) + expect(suggestedResultsPanel.prop('results')).toEqual([ + { + id: { + raw: '4', + snippet: null, + }, + foo: { + raw: 'foo', + snippet: null, + }, + _meta: { + id: '4', + }, + }, + ]); + }); + it('shows suggested promoted documents', () => { const wrapper = shallow(); const suggestedResultsPanel = wrapper.find(CurationResultPanel).at(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx index ade78e4914e84..3191d4e912cff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx @@ -25,6 +25,7 @@ import { EngineLogic } from '../../../engine'; import { AppSearchPageTemplate } from '../../../layout'; import { Result } from '../../../result'; import { Result as ResultType } from '../../../result/types'; +import { convertToResultFormat } from '../../curation/results'; import { getCurationsBreadcrumbs } from '../../utils'; import { CurationActionBar } from './curation_action_bar'; @@ -35,14 +36,15 @@ import { DATA } from './temp_data'; export const CurationSuggestion: React.FC = () => { const { query } = useDecodedParams(); + const { engine, isMetaEngine } = useValues(EngineLogic); const curationSuggestionLogic = CurationSuggestionLogic({ query }); const { loadSuggestion } = useActions(curationSuggestionLogic); - const { engine, isMetaEngine } = useValues(EngineLogic); - const { suggestion, suggestedPromotedDocuments, dataLoading } = + const { suggestion, suggestedPromotedDocuments, curation, dataLoading } = useValues(curationSuggestionLogic); const [showOrganicResults, setShowOrganicResults] = useState(false); const currentOrganicResults = [...DATA].splice(5, 4); const proposedOrganicResults = [...DATA].splice(2, 4); + const existingCurationResults = curation ? curation.promoted.map(convertToResultFormat) : []; const suggestionQuery = suggestion?.query || ''; @@ -79,7 +81,7 @@ export const CurationSuggestion: React.FC = () => { - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts index 9edeab4b658ef..af694c3756fd1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts @@ -11,6 +11,8 @@ import { mockHttpValues, } from '../../../../../__mocks__/kea_logic'; +import { set } from 'lodash/fp'; + import '../../../../__mocks__/engine_logic.mock'; import { nextTick } from '@kbn/test/jest'; @@ -23,6 +25,7 @@ const DEFAULT_VALUES = { dataLoading: true, suggestion: null, suggestedPromotedDocuments: [], + curation: null, }; const suggestion: CurationSuggestion = { @@ -32,6 +35,19 @@ const suggestion: CurationSuggestion = { status: 'applied', }; +const curation = { + id: 'cur-6155e69c7a2f2e4f756303fd', + queries: ['foo'], + promoted: [ + { + id: '5', + }, + ], + hidden: [], + last_updated: 'September 30, 2021 at 04:32PM', + organic: [], +}; + const suggestedPromotedDocuments = [ { id: { @@ -117,16 +133,18 @@ describe('CurationSuggestionLogic', () => { describe('actions', () => { describe('onSuggestionLoaded', () => { - it('should save the loaded suggestion and promoted documents associated with that suggestion and set dataLoading to false', () => { + it('should save provided state and set dataLoading to false', () => { mountLogic(); CurationSuggestionLogic.actions.onSuggestionLoaded({ suggestion, suggestedPromotedDocuments, + curation, }); expect(CurationSuggestionLogic.values).toEqual({ ...DEFAULT_VALUES, suggestion, suggestedPromotedDocuments, + curation, dataLoading: false, }); }); @@ -146,7 +164,7 @@ describe('CurationSuggestionLogic', () => { }); }); - it('should make an API call and trigger onSuggestionLoaded', async () => { + it('should make API calls to fetch data and trigger onSuggestionLoaded', async () => { http.post.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); http.post.mockReturnValueOnce(Promise.resolve(MOCK_DOCUMENTS_RESPONSE)); mountLogic(); @@ -213,6 +231,36 @@ describe('CurationSuggestionLogic', () => { }, }, ], + curation: null, + }); + }); + + it('will also fetch curation details if the suggestion has a curation_id', async () => { + http.post.mockReturnValueOnce( + Promise.resolve( + set('results[0].curation_id', 'cur-6155e69c7a2f2e4f756303fd', MOCK_RESPONSE) + ) + ); + http.post.mockReturnValueOnce(Promise.resolve(MOCK_DOCUMENTS_RESPONSE)); + http.get.mockReturnValueOnce(Promise.resolve(curation)); + mountLogic({ + suggestion: set('curation_id', 'cur-6155e69c7a2f2e4f756303fd', suggestion), + }); + jest.spyOn(CurationSuggestionLogic.actions, 'onSuggestionLoaded'); + + CurationSuggestionLogic.actions.loadSuggestion(); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith( + '/internal/app_search/engines/some-engine/curations/cur-6155e69c7a2f2e4f756303fd', + { query: { skip_record_analytics: 'true' } } + ); + await nextTick(); + + expect(CurationSuggestionLogic.actions.onSuggestionLoaded).toHaveBeenCalledWith({ + suggestion: expect.any(Object), + suggestedPromotedDocuments: expect.any(Object), + curation, }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts index d3f27be122060..3c3af8bfd96c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts @@ -6,17 +6,19 @@ */ import { kea, MakeLogicType } from 'kea'; +import { HttpSetup } from 'kibana/public'; import { flashAPIErrors } from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; import { EngineLogic } from '../../../engine'; import { Result } from '../../../result/types'; -import { CurationSuggestion } from '../../types'; +import { Curation, CurationSuggestion } from '../../types'; interface CurationSuggestionValues { dataLoading: boolean; suggestion: CurationSuggestion | null; suggestedPromotedDocuments: Result[]; + curation: Curation | null; } interface CurationSuggestionActions { @@ -24,12 +26,15 @@ interface CurationSuggestionActions { onSuggestionLoaded({ suggestion, suggestedPromotedDocuments, + curation, }: { suggestion: CurationSuggestion; suggestedPromotedDocuments: Result[]; + curation: Curation; }): { suggestion: CurationSuggestion; suggestedPromotedDocuments: Result[]; + curation: Curation; }; } @@ -43,9 +48,10 @@ export const CurationSuggestionLogic = kea< path: ['enterprise_search', 'app_search', 'curations', 'suggestion_logic'], actions: () => ({ loadSuggestion: true, - onSuggestionLoaded: ({ suggestion, suggestedPromotedDocuments }) => ({ + onSuggestionLoaded: ({ suggestion, suggestedPromotedDocuments, curation }) => ({ suggestion, suggestedPromotedDocuments, + curation, }), }), reducers: () => ({ @@ -68,6 +74,12 @@ export const CurationSuggestionLogic = kea< onSuggestionLoaded: (_, { suggestedPromotedDocuments }) => suggestedPromotedDocuments, }, ], + curation: [ + null, + { + onSuggestionLoaded: (_, { curation }) => curation, + }, + ], }), listeners: ({ actions, props }) => ({ loadSuggestion: async () => { @@ -75,44 +87,20 @@ export const CurationSuggestionLogic = kea< const { engineName } = EngineLogic.values; try { - const response = await http.post( - `/internal/app_search/engines/${engineName}/search_relevance_suggestions/${props.query}`, - { - body: JSON.stringify({ - page: { - current: 1, - size: 1, - }, - filters: { - status: ['pending'], - type: 'curation', - }, - }), - } - ); + const suggestion = await getSuggestions(http, engineName, props.query); + const promotedIds: string[] = suggestion.promoted; + const documentDetailsResopnse = getDocumentDetails(http, engineName, promotedIds); - const suggestion = response.results[0]; + let promises = [documentDetailsResopnse]; + if (suggestion.curation_id) { + promises = [...promises, getCuration(http, engineName, suggestion.curation_id)]; + } - const searchResponse = await http.post( - `/internal/app_search/engines/${engineName}/search`, - { - query: { query: '' }, - body: JSON.stringify({ - page: { - size: 100, - }, - filters: { - id: suggestion.promoted, - }, - }), - } - ); + const [documentDetails, curation] = await Promise.all(promises); // Filter out docs that were not found and maintain promoted order - const promotedIds: string[] = suggestion.promoted; - const documentDetails = searchResponse.results; const suggestedPromotedDocuments = promotedIds.reduce((acc: Result[], id: string) => { - const found = documentDetails.find( + const found = documentDetails.results.find( (documentDetail: Result) => documentDetail.id.raw === id ); if (!found) return acc; @@ -120,8 +108,9 @@ export const CurationSuggestionLogic = kea< }, []); actions.onSuggestionLoaded({ - suggestion: suggestion as CurationSuggestion, + suggestion, suggestedPromotedDocuments, + curation: curation || null, }); } catch (e) { flashAPIErrors(e); @@ -129,3 +118,48 @@ export const CurationSuggestionLogic = kea< }, }), }); + +const getSuggestions = async ( + http: HttpSetup, + engineName: string, + query: string +): Promise => { + const response = await http.post( + `/internal/app_search/engines/${engineName}/search_relevance_suggestions/${query}`, + { + body: JSON.stringify({ + page: { + current: 1, + size: 1, + }, + filters: { + status: ['pending'], + type: 'curation', + }, + }), + } + ); + + const suggestion = response.results[0] as CurationSuggestion; + return suggestion; +}; + +const getDocumentDetails = async (http: HttpSetup, engineName: string, documentIds: string[]) => { + return http.post(`/internal/app_search/engines/${engineName}/search`, { + query: { query: '' }, + body: JSON.stringify({ + page: { + size: 100, + }, + filters: { + id: documentIds, + }, + }), + }); +}; + +const getCuration = async (http: HttpSetup, engineName: string, curationId: string) => { + return http.get(`/internal/app_search/engines/${engineName}/curations/${curationId}`, { + query: { skip_record_analytics: 'true' }, + }); +};