[App Search] Automated Curations UX improvements (#115773)

This commit is contained in:
Byron Hulcher 2021-10-20 16:56:14 -04:00 committed by GitHub
parent e3f4107d90
commit abd5e9ffa8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 117 additions and 71 deletions

View file

@ -49,6 +49,7 @@ describe('AutomatedCuration', () => {
const actions = {
convertToManual: jest.fn(),
onSelectPageTab: jest.fn(),
};
beforeEach(() => {
@ -62,48 +63,41 @@ describe('AutomatedCuration', () => {
const wrapper = shallow(<AutomatedCuration />);
expect(wrapper.is(AppSearchPageTemplate));
expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
expect(wrapper.find(History)).toHaveLength(0);
});
it('includes tabs', () => {
it('includes set of tabs in the page header', () => {
const wrapper = shallow(<AutomatedCuration />);
let tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs).toHaveLength(3);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(0).prop('isSelected')).toBe(true);
tabs.at(0).simulate('click');
expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(1, 'promoted');
expect(tabs.at(1).prop('onClick')).toBeUndefined();
expect(tabs.at(1).prop('isSelected')).toBe(false);
expect(tabs.at(1).prop('disabled')).toBe(true);
expect(tabs.at(2).prop('isSelected')).toBe(false);
// Clicking on the History tab shows the history view
tabs.at(2).simulate('click');
expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(2, 'history');
});
tabs = getPageHeaderTabs(wrapper).find(EuiTab);
it('renders promoted and organic documents when the promoted tab is selected', () => {
setMockValues({ ...values, selectedPageTab: 'promoted' });
const wrapper = shallow(<AutomatedCuration />);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(0).prop('isSelected')).toBe(false);
expect(tabs.at(2).prop('isSelected')).toBe(true);
expect(wrapper.find(PromotedDocuments)).toHaveLength(0);
expect(wrapper.find(OrganicDocuments)).toHaveLength(0);
expect(wrapper.find(History)).toHaveLength(1);
// Clicking back to the Promoted tab shows promoted documents
tabs.at(0).simulate('click');
tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(0).prop('isSelected')).toBe(true);
expect(tabs.at(2).prop('isSelected')).toBe(false);
expect(tabs.at(0).prop('isSelected')).toEqual(true);
expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
expect(wrapper.find(History)).toHaveLength(0);
});
it('renders curation history when the history tab is selected', () => {
setMockValues({ ...values, selectedPageTab: 'history' });
const wrapper = shallow(<AutomatedCuration />);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(2).prop('isSelected')).toEqual(true);
expect(wrapper.find(History)).toHaveLength(1);
});
it('initializes CurationLogic with a curationId prop from URL param', () => {

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useState } from 'react';
import React from 'react';
import { useParams } from 'react-router-dom';
import { useValues, useActions } from 'kea';
@ -31,23 +31,19 @@ import { DeleteCurationButton } from './delete_curation_button';
import { PromotedDocuments, OrganicDocuments } from './documents';
import { History } from './history';
const PROMOTED = 'promoted';
const HISTORY = 'history';
export const AutomatedCuration: React.FC = () => {
const { curationId } = useParams<{ curationId: string }>();
const logic = CurationLogic({ curationId });
const { convertToManual } = useActions(logic);
const { activeQuery, dataLoading, queries, curation } = useValues(logic);
const { convertToManual, onSelectPageTab } = useActions(logic);
const { activeQuery, dataLoading, queries, curation, selectedPageTab } = useValues(logic);
const { engineName } = useValues(EngineLogic);
const [selectedPageTab, setSelectedPageTab] = useState(PROMOTED);
const pageTabs = [
{
label: PROMOTED_DOCUMENTS_TITLE,
append: <EuiBadge>{curation.promoted.length}</EuiBadge>,
isSelected: selectedPageTab === PROMOTED,
onClick: () => setSelectedPageTab(PROMOTED),
isSelected: selectedPageTab === 'promoted',
onClick: () => onSelectPageTab('promoted'),
},
{
label: HIDDEN_DOCUMENTS_TITLE,
@ -62,8 +58,8 @@ export const AutomatedCuration: React.FC = () => {
defaultMessage: 'History',
}
),
isSelected: selectedPageTab === HISTORY,
onClick: () => setSelectedPageTab(HISTORY),
isSelected: selectedPageTab === 'history',
onClick: () => onSelectPageTab('history'),
},
];
@ -102,9 +98,9 @@ export const AutomatedCuration: React.FC = () => {
}}
isLoading={dataLoading}
>
{selectedPageTab === PROMOTED && <PromotedDocuments />}
{selectedPageTab === PROMOTED && <OrganicDocuments />}
{selectedPageTab === HISTORY && (
{selectedPageTab === 'promoted' && <PromotedDocuments />}
{selectedPageTab === 'promoted' && <OrganicDocuments />}
{selectedPageTab === 'history' && (
<History query={curation.queries[0]} engineName={engineName} />
)}
</AppSearchPageTemplate>

View file

@ -250,7 +250,7 @@ describe('CurationLogic', () => {
});
describe('onSelectPageTab', () => {
it('should set the selected page tab', () => {
it('should set the selected page tab and clears flash messages', () => {
mount({
selectedPageTab: 'promoted',
});
@ -261,6 +261,7 @@ describe('CurationLogic', () => {
...DEFAULT_VALUES,
selectedPageTab: 'hidden',
});
expect(clearFlashMessages).toHaveBeenCalled();
});
});
});

View file

@ -21,7 +21,7 @@ import { DELETE_SUCCESS_MESSAGE } from '../constants';
import { Curation } from '../types';
import { addDocument, removeDocument } from '../utils';
type CurationPageTabs = 'promoted' | 'hidden';
type CurationPageTabs = 'promoted' | 'history' | 'hidden';
interface CurationValues {
dataLoading: boolean;
@ -271,6 +271,9 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
actions.updateCuration();
},
onSelectPageTab: () => {
clearFlashMessages();
},
setActiveQuery: () => actions.updateCuration(),
setPromotedIds: () => actions.updateCuration(),
addPromotedId: () => actions.updateCuration(),

View file

@ -22,7 +22,7 @@ jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
import { CurationLogic } from './curation_logic';
import { DeleteCurationButton } from './delete_curation_button';
import { PromotedDocuments, HiddenDocuments } from './documents';
import { PromotedDocuments, HiddenDocuments, OrganicDocuments } from './documents';
import { ManualCuration } from './manual_curation';
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
import { AddResultFlyout } from './results';
@ -74,13 +74,13 @@ describe('ManualCuration', () => {
expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(2, 'hidden');
});
it('contains a suggested documents callout when the selectedPageTab is ', () => {
it('contains a suggested documents callout', () => {
const wrapper = shallow(<ManualCuration />);
expect(wrapper.find(SuggestedDocumentsCallout)).toHaveLength(1);
});
it('renders promoted documents when that tab is selected', () => {
it('renders promoted and organic documents when the promoted tab is selected', () => {
setMockValues({ ...values, selectedPageTab: 'promoted' });
const wrapper = shallow(<ManualCuration />);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
@ -88,9 +88,10 @@ describe('ManualCuration', () => {
expect(tabs.at(0).prop('isSelected')).toEqual(true);
expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
});
it('renders hidden documents when that tab is selected', () => {
it('renders hidden documents when the hidden tab is selected', () => {
setMockValues({ ...values, selectedPageTab: 'hidden' });
const wrapper = shallow(<ManualCuration />);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);

View file

@ -26,10 +26,10 @@ import { SuggestedDocumentsCallout } from './suggested_documents_callout';
export const ManualCuration: React.FC = () => {
const { curationId } = useParams() as { curationId: string };
const { onSelectPageTab } = useActions(CurationLogic({ curationId }));
const { dataLoading, queries, selectedPageTab, curation } = useValues(
CurationLogic({ curationId })
);
const logic = CurationLogic({ curationId });
const { onSelectPageTab } = useActions(logic);
const { dataLoading, queries, selectedPageTab, curation } = useValues(logic);
const { isFlyoutOpen } = useValues(AddResultLogic);
const pageTabs = [

View file

@ -92,7 +92,7 @@ describe('CurationsLogic', () => {
});
describe('onSelectPageTab', () => {
it('should set the selected page tab', () => {
it('should set the selected page tab and clear flash messages', () => {
mount();
CurationsLogic.actions.onSelectPageTab('settings');
@ -101,6 +101,7 @@ describe('CurationsLogic', () => {
...DEFAULT_VALUES,
selectedPageTab: 'settings',
});
expect(clearFlashMessages).toHaveBeenCalled();
});
});
});

View file

@ -126,5 +126,8 @@ export const CurationsLogic = kea<MakeLogicType<CurationsValues, CurationsAction
flashAPIErrors(e);
}
},
onSelectPageTab: () => {
clearFlashMessages();
},
}),
});

View file

@ -94,7 +94,7 @@ describe('CurationsRouter', () => {
expect(MOCK_ACTIONS.loadCurationsSettings).toHaveBeenCalledTimes(1);
});
it('skips loading curation settings when log retention is enabled', () => {
it('skips loading curation settings when log retention is disabled', () => {
setMockValues({
...MOCK_VALUES,
logRetention: {

View file

@ -137,7 +137,7 @@ export const CurationSuggestionLogic = kea<
setQueuedSuccessMessage(
i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAppliedMessage',
{ defaultMessage: 'Suggestion was succefully applied.' }
{ defaultMessage: 'Suggestion was successfully applied.' }
)
);
if (suggestion!.operation === 'delete') {
@ -177,7 +177,7 @@ export const CurationSuggestionLogic = kea<
'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAutomatedMessage',
{
defaultMessage:
'Suggestion was succefully applied and all future suggestions for the query "{query}" will be automatically applied.',
'Suggestion was successfully applied and all future suggestions for the query "{query}" will be automatically applied.',
values: { query: suggestion!.query },
}
)
@ -208,7 +208,7 @@ export const CurationSuggestionLogic = kea<
i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyRejectedMessage',
{
defaultMessage: 'Suggestion was succefully rejected.',
defaultMessage: 'Suggestion was successfully rejected.',
}
)
);
@ -230,7 +230,7 @@ export const CurationSuggestionLogic = kea<
'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyDisabledMessage',
{
defaultMessage:
'Suggestion was succefully rejected and you will no longer receive suggestions for the query "{query}".',
'Suggestion was successfully rejected and you will no longer receive suggestions for the query "{query}".',
values: { query: suggestion!.query },
}
)

View file

@ -47,6 +47,10 @@ describe('Curations', () => {
},
},
selectedPageTab: 'overview',
// CurationsSettingsLogic
curationsSettings: {
enabled: true,
},
// LicensingLogic
hasPlatinumLicense: true,
};
@ -78,8 +82,6 @@ describe('Curations', () => {
tabs.at(2).simulate('click');
expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(3, 'settings');
// The settings tab should NOT have an icon next to it
expect(tabs.at(2).prop('prepend')).toBeUndefined();
});
it('renders less tabs when less than platinum license', () => {
@ -90,8 +92,47 @@ describe('Curations', () => {
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.length).toBe(2);
// The settings tab should have an icon next to it
expect(tabs.at(1).prop('prepend')).not.toBeUndefined();
});
it('renders a New! badge when less than platinum license', () => {
setMockValues({ ...values, hasPlatinumLicense: false });
const wrapper = shallow(<Curations />);
expect(getPageTitle(wrapper)).toEqual('Curated results');
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(1).prop('append')).not.toBeUndefined();
});
it('renders a New! badge when suggestions are disabled', () => {
setMockValues({
...values,
curationsSettings: {
enabled: false,
},
});
const wrapper = shallow(<Curations />);
expect(getPageTitle(wrapper)).toEqual('Curated results');
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(2).prop('append')).not.toBeUndefined();
});
it('hides the badge when suggestions are enabled and the user has a platinum license', () => {
setMockValues({
...values,
hasPlatinumLicense: true,
curationsSettings: {
enabled: true,
},
});
const wrapper = shallow(<Curations />);
expect(getPageTitle(wrapper)).toEqual('Curated results');
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(2).prop('append')).toBeUndefined();
});
it('renders an overview view', () => {

View file

@ -9,7 +9,7 @@ import React, { useEffect } from 'react';
import { useValues, useActions } from 'kea';
import { EuiIcon } from '@elastic/eui';
import { EuiBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { LicensingLogic } from '../../../../shared/licensing';
@ -31,7 +31,12 @@ export const Curations: React.FC = () => {
const { dataLoading: curationsDataLoading, meta, selectedPageTab } = useValues(CurationsLogic);
const { loadCurations, onSelectPageTab } = useActions(CurationsLogic);
const { hasPlatinumLicense } = useValues(LicensingLogic);
const { dataLoading: curationsSettingsDataLoading } = useValues(CurationsSettingsLogic);
const {
dataLoading: curationsSettingsDataLoading,
curationsSettings: { enabled: curationsSettingsEnabled },
} = useValues(CurationsSettingsLogic);
const suggestionsEnabled = hasPlatinumLicense && curationsSettingsEnabled;
const OVERVIEW_TAB = {
label: i18n.translate(
@ -61,17 +66,18 @@ export const Curations: React.FC = () => {
),
isSelected: selectedPageTab === 'settings',
onClick: () => onSelectPageTab('settings'),
append: suggestionsEnabled ? undefined : (
<EuiBadge color="success">
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.newBadgeLabel', {
defaultMessage: 'New!',
})}
</EuiBadge>
),
};
const pageTabs = hasPlatinumLicense
? [OVERVIEW_TAB, HISTORY_TAB, SETTINGS_TAB]
: [
OVERVIEW_TAB,
{
...SETTINGS_TAB,
prepend: <EuiIcon type="cheer" />,
},
];
: [OVERVIEW_TAB, SETTINGS_TAB];
useEffect(() => {
loadCurations();