diff --git a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts index 5ed2c6893be5f1..5b6b847643206b 100644 --- a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts +++ b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts @@ -41,6 +41,7 @@ export const DEFAULT_INITIAL_APP_DATA = { }, appSearch: { accountId: 'some-id-string', + kibanaUIsEnabled: true, onboardingComplete: true, role: { id: 'account_id:somestring|user_oid:somestring', diff --git a/x-pack/plugins/enterprise_search/common/types/app_search.ts b/x-pack/plugins/enterprise_search/common/types/app_search.ts index 30c6dd519db49a..cb5e2ec46f04d3 100644 --- a/x-pack/plugins/enterprise_search/common/types/app_search.ts +++ b/x-pack/plugins/enterprise_search/common/types/app_search.ts @@ -7,6 +7,7 @@ export interface Account { accountId: string; + kibanaUIsEnabled: boolean; onboardingComplete: boolean; role: { id: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts index c9b5afe2f6cf42..35dafbeb33178a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts @@ -30,10 +30,12 @@ describe('AppLogic', () => { }, account: { accountId: 'some-id-string', + kibanaUIsEnabled: true, onboardingComplete: true, role: DEFAULT_INITIAL_APP_DATA.appSearch.role, }, myRole: {}, + showGateForm: false, }; it('sets values from props', () => { @@ -48,6 +50,7 @@ describe('AppLogic', () => { }, account: { accountId: 'some-id-string', + kibanaUIsEnabled: true, onboardingComplete: true, role: DEFAULT_INITIAL_APP_DATA.appSearch.role, }, @@ -60,6 +63,7 @@ describe('AppLogic', () => { canViewAccountCredentials: true, // Truncated for brevity - see utils/role/index.test.ts for full output }), + showGateForm: false, }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts index dec60a4e1eb347..26187e16f05a9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts @@ -16,10 +16,12 @@ import { ConfiguredLimits, Account, Role } from './types'; import { getRoleAbilities } from './utils/role'; interface AppValues { - configuredLimits: ConfiguredLimits; account: Account; + showGateForm: boolean; + configuredLimits: ConfiguredLimits; myRole: Role; } + interface AppActions { setOnboardingComplete(): boolean; } @@ -41,6 +43,10 @@ export const AppLogic = kea { + switch (id) { + case featuresList.webCrawler.id: + return featuresList.webCrawler; + case featuresList.analyticsAndLogs.id: + return featuresList.analyticsAndLogs; + case featuresList.synonyms.id: + return featuresList.synonyms; + case featuresList.relevanceTuning.id: + return featuresList.relevanceTuning; + case featuresList.curations.id: + return featuresList.curations; + case featuresList.searchManagementUis.id: + return featuresList.searchManagementUis; + case featuresList.credentials.id: + return featuresList.credentials; + default: + return undefined; + } +}; + +interface FeatureOptionsSelection { + dropdownDisplay: React.ReactNode; + inputDisplay: string; + value: string; +} + +const getOptionsFeaturesList = (): FeatureOptionsSelection[] => { + const baseTranslatePrefix = 'xpack.enterpriseSearch.appSearch.gateForm.superSelect'; + + const featureList = Object.keys(featuresList).map((featureKey): FeatureOptionsSelection => { + const feature = getFeature(featureKey); + if (!feature) { + return { + dropdownDisplay: <>, + inputDisplay: '', + value: '', + }; + } + + return { + dropdownDisplay: ( + <> + {feature.title} + +

{feature.description}

+
+ + ), + inputDisplay: feature.title, + value: feature.id, + }; + }); + + featureList.push({ + dropdownDisplay: ( + <> + + {i18n.translate(`${baseTranslatePrefix}.other.title`, { + defaultMessage: 'Other', + })} + + +

+ {i18n.translate(`${baseTranslatePrefix}.other.description`, { + defaultMessage: 'Another feature not listed here', + })} +

+
+ + ), + inputDisplay: i18n.translate(`${baseTranslatePrefix}.other.inputDisplay`, { + defaultMessage: 'Other', + }), + value: 'other', + }); + + return featureList; +}; + +const participateInUXLabsChoice = { + no: { choice: 'no', value: false }, + yes: { choice: 'yes', value: true }, +}; + +const EducationPanel: React.FC<{ featureContent: string }> = ({ featureContent }) => { + const feature = getFeature(featureContent); + const { setFeaturesOther } = useActions(AppSearchGateLogic); + if (feature) { + return ( + + + + + + + + + +
+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.title', + { + defaultMessage: 'Elasticsearch native equivalent', + } + )} +
+
+
+ + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.subTitle', + { + defaultMessage: 'Based on your selection we recommend:', + } + )} +

+
+
+
+
+
+ + + +

{feature.panelText}

+ + {feature.actionLink !== undefined && feature.actionLabel !== undefined && ( + + + {feature.actionLabel} + + + )} + + {feature.learnMore !== undefined && ( + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.learnMore', + { + defaultMessage: 'Learn more', + } + )} + + + )} + + {feature.addOnLearnMoreLabel !== undefined && + feature.addOnLearnMoreUrl !== undefined && ( + + + + {feature.addOnLearnMoreLabel} + + + )} + +
+
+ ); + } else { + return ( + <> + + + { + setFeaturesOther(e.target.value); + }} + /> + + + ); + } +}; + +export const AppSearchGate: React.FC = () => { + const { feature, participateInUXLabs } = useValues(AppSearchGateLogic); + const { formSubmitRequest, setAdditionalFeedback, setParticipateInUXLabs, setFeature } = + useActions(AppSearchGateLogic); + const options = getOptionsFeaturesList(); + return ( + + + + {i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.features.Label', { + defaultMessage: 'What App Search feature are you looking to use?', + })} + + + + setFeature(value)} + itemLayoutAlign="top" + hasDividers + fullWidth + /> + + {feature && } + + + + + + { + setAdditionalFeedback(e.target.value); + }} + /> + + + + + + + ), + privacyStatementLink: ( + + + + ), + termsOfService: ( + + + + ), + }} + /> + + + + + + + + + + setParticipateInUXLabs( + e.target.value === participateInUXLabsChoice.yes.choice + ? participateInUXLabsChoice.yes.value + : participateInUXLabsChoice.no.value + ) + } + value={ + participateInUXLabs !== null + ? participateInUXLabs + ? participateInUXLabsChoice.yes.choice + : participateInUXLabsChoice.no.choice + : undefined + } + /> + + + + + + formSubmitRequest()} + > + {i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.submit', { + defaultMessage: 'Submit', + })} + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.test.ts new file mode 100644 index 00000000000000..493eaae3e20bd4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.test.ts @@ -0,0 +1,36 @@ +/* + * 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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { sendAppSearchGatedFormData } from './app_search_gate_api_logic'; + +describe('AppSearchGatedFormApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('sendAppSearchGatedFormData', () => { + it('calls correct api', async () => { + const asFormData = { + additionalFeedback: 'my-test-additional-data', + feature: 'Web Crawler', + featuresOther: null, + participateInUXLabs: null, + }; + const promise = Promise.resolve(); + http.put.mockReturnValue(promise); + sendAppSearchGatedFormData(asFormData); + await nextTick(); + expect(http.post).toHaveBeenCalledWith('/internal/app_search/as_gate', { + body: '{"as_gate_data":{"additional_feedback":"my-test-additional-data","feature":"Web Crawler"}}', + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.ts new file mode 100644 index 00000000000000..a79904385de94e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.ts @@ -0,0 +1,51 @@ +/* + * 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 { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface AppSearchGatedFormDataApiLogicArguments { + additionalFeedback: string | null; + feature: string; + featuresOther: string | null; + participateInUXLabs: boolean | null; +} + +export interface AppSearchGatedFormDataApiLogicResponse { + created: string; +} + +export const sendAppSearchGatedFormData = async ({ + feature, + featuresOther, + additionalFeedback, + participateInUXLabs, +}: AppSearchGatedFormDataApiLogicArguments): Promise => { + return await HttpLogic.values.http.post( + '/internal/app_search/as_gate', + { + body: JSON.stringify({ + as_gate_data: { + additional_feedback: additionalFeedback != null ? additionalFeedback : undefined, + feature, + features_other: featuresOther != null ? featuresOther : undefined, + participate_in_ux_labs: participateInUXLabs != null ? participateInUXLabs : undefined, + }, + }), + } + ); +}; + +export type AppSearchGatedFormDataApiLogicActions = Actions< + AppSearchGatedFormDataApiLogicArguments, + AppSearchGatedFormDataApiLogicResponse +>; + +export const UpdateAppSearchGatedFormDataApiLogic = createApiLogic( + ['app_search', 'send_app_search_gatedForm_data_api_logic'], + sendAppSearchGatedFormData +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.test.ts new file mode 100644 index 00000000000000..bb3f94a37d9e57 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.test.ts @@ -0,0 +1,57 @@ +/* + * 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 { LogicMounter } from '../../../__mocks__/kea_logic'; + +import { UpdateAppSearchGatedFormDataApiLogic } from './app_search_gate_api_logic'; +import { AppSearchGateLogic } from './app_search_gate_logic'; + +const DEFAULT_VALUES = { + additionalFeedback: null, + feature: '', + featuresOther: null, + participateInUXLabs: null, +}; + +describe('Gated form data', () => { + const { mount: apiLogicMount } = new LogicMounter(UpdateAppSearchGatedFormDataApiLogic); + const { mount } = new LogicMounter(AppSearchGateLogic); + beforeEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + apiLogicMount(); + mount(); + }); + it('has expected default values', () => { + expect(AppSearchGateLogic.values).toEqual(DEFAULT_VALUES); + }); + describe('listeners', () => { + it('make Request with only feature, participateInUXLabs response ', () => { + jest.spyOn(AppSearchGateLogic.actions, 'submitGatedFormDataRequest'); + + AppSearchGateLogic.actions.setFeature('Web Crawler'); + AppSearchGateLogic.actions.setParticipateInUXLabs(false); + + AppSearchGateLogic.actions.formSubmitRequest(); + + expect(AppSearchGateLogic.actions.submitGatedFormDataRequest).toHaveBeenCalledTimes(1); + expect(AppSearchGateLogic.actions.submitGatedFormDataRequest).toHaveBeenCalledWith({ + additionalFeedback: null, + feature: 'Web Crawler', + featuresOther: null, + participateInUXLabs: false, + }); + }); + + it('when no feature selected, formSubmitRequest is not called', () => { + jest.spyOn(AppSearchGateLogic.actions, 'submitGatedFormDataRequest'); + AppSearchGateLogic.actions.formSubmitRequest(); + + expect(AppSearchGateLogic.actions.submitGatedFormDataRequest).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.ts new file mode 100644 index 00000000000000..0cce74ef59de19 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.ts @@ -0,0 +1,98 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { + AppSearchGatedFormDataApiLogicActions, + UpdateAppSearchGatedFormDataApiLogic, +} from './app_search_gate_api_logic'; + +interface AppSearchGateValues { + additionalFeedback: string | null; + feature: string; + featuresOther: string | null; + participateInUXLabs: boolean | null; +} + +interface AppSearchGateActions { + formSubmitRequest: () => void; + setAdditionalFeedback(additionalFeedback: string): { additionalFeedback: string }; + setFeature(feature: string): { feature: string }; + setFeaturesOther(featuresOther: string): { featuresOther: string }; + setParticipateInUXLabs(participateInUXLabs: boolean): { + participateInUXLabs: boolean; + }; + submitGatedFormDataRequest: AppSearchGatedFormDataApiLogicActions['makeRequest']; +} + +export const AppSearchGateLogic = kea>({ + actions: { + formSubmitRequest: true, + setAdditionalFeedback: (additionalFeedback) => ({ additionalFeedback }), + setFeature: (feature) => ({ feature }), + setFeaturesOther: (featuresOther) => ({ featuresOther }), + setParticipateInUXLabs: (participateInUXLabs) => ({ participateInUXLabs }), + }, + connect: { + actions: [UpdateAppSearchGatedFormDataApiLogic, ['makeRequest as submitGatedFormDataRequest']], + }, + listeners: ({ actions, values }) => ({ + formSubmitRequest: () => { + if (values.feature) { + actions.submitGatedFormDataRequest({ + additionalFeedback: values?.additionalFeedback ? values?.additionalFeedback : null, + feature: values.feature, + featuresOther: values?.featuresOther ? values?.featuresOther : null, + participateInUXLabs: values?.participateInUXLabs, + }); + } + }, + }), + path: ['enterprise_search', 'app_search', 'gate_form'], + + reducers: ({}) => ({ + additionalFeedback: [ + null, + { + setAdditionalFeedback: ( + _: AppSearchGateValues['additionalFeedback'], + { additionalFeedback }: { additionalFeedback: AppSearchGateValues['additionalFeedback'] } + ): AppSearchGateValues['additionalFeedback'] => additionalFeedback ?? null, + }, + ], + feature: [ + '', + { + setFeature: ( + _: AppSearchGateValues['feature'], + { feature }: { feature: AppSearchGateValues['feature'] } + ): AppSearchGateValues['feature'] => feature ?? '', + }, + ], + featuresOther: [ + null, + { + setFeaturesOther: ( + _: AppSearchGateValues['featuresOther'], + { featuresOther }: { featuresOther: AppSearchGateValues['featuresOther'] } + ): AppSearchGateValues['featuresOther'] => featuresOther ?? null, + }, + ], + participateInUXLabs: [ + null, + { + setParticipateInUXLabs: ( + _: AppSearchGateValues['participateInUXLabs'], + { + participateInUXLabs, + }: { participateInUXLabs: AppSearchGateValues['participateInUXLabs'] } + ): AppSearchGateValues['participateInUXLabs'] => participateInUXLabs ?? null, + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gated_form_page.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gated_form_page.tsx new file mode 100644 index 00000000000000..b25c4baf1e00c9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gated_form_page.tsx @@ -0,0 +1,51 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../common/constants'; + +import { + EnterpriseSearchPageTemplateWrapper, + PageTemplateProps, + useEnterpriseSearchNav, +} from '../../../shared/layout'; +import { SendAppSearchTelemetry } from '../../../shared/telemetry'; + +import { AppSearchGate } from './app_search_gate'; + +export const AppSearchGatePage: React.FC = ({ isLoading }) => { + return ( + + ), + pageTitle: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.title', { + defaultMessage: 'Before you begin...', + }), + }} + solutionNav={{ + items: useEnterpriseSearchNav(), + name: ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAME, + }} + isLoading={isLoading} + hideEmbeddedConsole + > + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/index.ts new file mode 100644 index 00000000000000..c724e9ab999e8e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { AppSearchGate } from './app_search_gate'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx index e7d11b0ad5ec57..164e0131ca57b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx @@ -14,6 +14,8 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { rerender } from '../../../test_helpers'; +import { AppSearchGatePage } from '../app_search_gate/app_search_gated_form_page'; + import { EnginesTable } from './components/tables/engines_table'; import { MetaEnginesTable } from './components/tables/meta_engines_table'; @@ -21,6 +23,12 @@ import { EnginesOverview } from '.'; describe('EnginesOverview', () => { const values = { + account: { + kibanaUIsEnabled: true, + role: { + roleType: 'owner', + }, + }, dataLoading: false, engines: [], enginesMeta: { @@ -46,7 +54,9 @@ describe('EnginesOverview', () => { // MetaEnginesTableLogic expandedSourceEngines: {}, conflictingEnginesSets: {}, + showGateForm: false, }; + const actions = { loadEngines: jest.fn(), loadMetaEngines: jest.fn(), @@ -78,10 +88,23 @@ describe('EnginesOverview', () => { setMockValues(valuesWithEngines); }); - it('renders and calls the engines API', () => { + it('does not render overview page when kibanaUIsEnabled is false', () => { + setMockValues({ + ...values, + showGateForm: true, + }); + const wrapper = shallow(); + + expect(wrapper.find(AppSearchGatePage)).toHaveLength(1); + expect(wrapper.find(EnginesTable)).toHaveLength(0); + expect(actions.loadEngines).toHaveBeenCalled(); + }); + + it('renders and calls the engines API kibanaUIsEnabled is true', () => { const wrapper = shallow(); expect(wrapper.find(EnginesTable)).toHaveLength(1); + expect(wrapper.find(AppSearchGatePage)).toHaveLength(0); expect(actions.loadEngines).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index cb4bb7d40f47a0..339924ec7b3d92 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -16,6 +16,7 @@ import { convertMetaToPagination, handlePageChange } from '../../../shared/table import { AppLogic } from '../../app_logic'; import { EngineIcon, MetaEngineIcon } from '../../icons'; import { ENGINE_CREATION_PATH, META_ENGINE_CREATION_PATH } from '../../routes'; +import { AppSearchGatePage } from '../app_search_gate/app_search_gated_form_page'; import { DataPanel } from '../data_panel'; import { AppSearchPageTemplate } from '../layout'; @@ -23,6 +24,7 @@ import { EmptyState, EmptyMetaEnginesState } from './components'; import { AuditLogsModal } from './components/audit_logs_modal/audit_logs_modal'; import { EnginesTable } from './components/tables/engines_table'; import { MetaEnginesTable } from './components/tables/meta_engines_table'; + import { ENGINES_OVERVIEW_TITLE, CREATE_AN_ENGINE_BUTTON_LABEL, @@ -34,6 +36,7 @@ import { EnginesLogic } from './engines_logic'; export const EnginesOverview: React.FC = () => { const { + showGateForm, myRole: { canManageEngines, canManageMetaEngines }, } = useValues(AppLogic); @@ -58,7 +61,7 @@ export const EnginesOverview: React.FC = () => { loadMetaEngines(); }, [metaEnginesMeta.page.current]); - return ( + return !showGateForm ? ( { + ) : ( + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 19cfa874fc76c3..bd8d6c30a0188f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -79,6 +79,21 @@ describe('AppSearchUnconfigured', () => { }); }); +describe('AppSearchConfigured showGateForm is true', () => { + let wrapper: ShallowWrapper; + const renderHeaderActions = jest.fn(); + + beforeAll(() => { + setMockValues({ showGateForm: true, myRole: {}, renderHeaderActions }); + wrapper = shallow(); + }); + + it('renders engine overview only when showGateForm is true', () => { + expect(wrapper.find(EnginesOverview)).toHaveLength(1); + expect(wrapper.find(EngineRouter)).toHaveLength(0); + }); +}); + describe('AppSearchConfigured', () => { let wrapper: ShallowWrapper; const renderHeaderActions = jest.fn(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 1c661f1f6d03bc..7f7237555867b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -93,6 +93,7 @@ export const AppSearchUnconfigured: React.FC = () => { export const AppSearchConfigured: React.FC> = (props) => { const { + showGateForm, myRole: { canManageEngines, canManageMetaEngines, @@ -107,7 +108,7 @@ export const AppSearchConfigured: React.FC> = (props) = renderHeaderActions(KibanaHeaderActions); }, []); - return ( + return !showGateForm ? ( {process.env.NODE_ENV === 'development' && ( @@ -152,5 +153,17 @@ export const AppSearchConfigured: React.FC> = (props) = + ) : ( + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx index 8f05aa57cca7ad..dedcc2fc53d072 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx @@ -55,10 +55,11 @@ const featuresList = { actionLabel: i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.action.Label', { - defaultMessage: 'Start with Behavioral Analytics', + defaultMessage: 'Add Search Analytics', } ), - actionLink: './analytics ', + actionLink: + 'https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-event.html', addOnLearnMoreLabel: undefined, addOnLearnMoreUrl: undefined, description: i18n.translate( @@ -69,7 +70,8 @@ const featuresList = { } ), id: 'Analytics', - learnMore: 'https://www.elastic.co/guide/en/enterprise-search/current/analytics-overview.html', + learnMore: + 'https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-overview.html', title: i18n.translate('xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureName', { defaultMessage: 'Use Behavioral Analytics', }), @@ -153,22 +155,22 @@ const featuresList = { actionLabel: i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.action.Label', { - defaultMessage: 'Create a Search Application', + defaultMessage: 'Build a search experience with Search UI', } ), - actionLink: './applications/search_applications', + actionLink: 'https://www.elastic.co/docs/current/search-ui/overview', addOnLearnMoreLabel: i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.addOn.learnMoreLabel', { defaultMessage: 'Search UI', } ), - addOnLearnMoreUrl: 'https://www.elastic.co/guide/en/enterprise-search/current/search-ui.html ', + addOnLearnMoreUrl: 'https://www.elastic.co/docs/current/search-ui/tutorials/elasticsearch', description: i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureDescription', { defaultMessage: - 'Did you know you can restrict access to documents in your Elasticsearch indices according to user and group permissions? Return only authorized search results for users with Elastic’s document level security. ', + 'Search-powered applications that leverage the full power of Elasticsearch! Build a unified search using Search Applications or integrate directly with your existing UI with Search UI.', } ), id: 'Search Application', @@ -241,7 +243,7 @@ const EducationPanel: React.FC<{ featureContent: string }> = ({ featureContent } {i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.subTitle', { - defaultMessage: 'Based on your selection we recommend you', + defaultMessage: 'Based on your selection we recommend:', } )}

diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index bae0aacec6ff4b..956d7f34277ce8 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -80,6 +80,7 @@ describe('callEnterpriseSearchConfigAPI', () => { app_search: { account: { id: 'some-id-string', + kibana_uis_enabled: true, onboarding_complete: true, }, role: { @@ -99,7 +100,7 @@ describe('callEnterpriseSearchConfigAPI', () => { organization: { name: 'ACME Donuts', default_org_name: 'My Organization', - kibanaUIsEnabled: false, + kibana_uis_enabled: false, }, account: { id: 'some-id-string', @@ -174,6 +175,7 @@ describe('callEnterpriseSearchConfigAPI', () => { }, appSearch: { accountId: undefined, + kibanaUIsEnabled: false, onboardingComplete: false, role: { id: undefined, diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index 90fccce31344bd..7b4af80f3f2e59 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -139,6 +139,7 @@ export const callEnterpriseSearchConfigAPI = async ({ }, appSearch: { accountId: data?.current_user?.app_search?.account?.id, + kibanaUIsEnabled: data?.current_user?.app_search?.account?.kibana_uis_enabled || false, onboardingComplete: !!data?.current_user?.app_search?.account?.onboarding_complete, role: { id: data?.current_user?.app_search?.role?.id, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/app_search_gated_form.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/app_search_gated_form.test.ts new file mode 100644 index 00000000000000..ef440cada1d7a4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/app_search_gated_form.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { registerAppSearchGatedFormRoute } from './app_search_gated_form'; + +describe('Overview route with kibana_uis_enabled ', () => { + describe('POST /internal/app_search/as_gate', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/internal/app_search/as_gate', + }); + + registerAppSearchGatedFormRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/api/ent/v2/internal/as_gate', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + as_gate_data: { + additional_feedback: '', + feature: 'Selected feature', + features_other: '', + participate_in_ux_labs: true, + }, + }, + }; + mockRouter.shouldValidate(request); + }); + + it('throws error unexpected values in body', () => { + const request = { + body: { + foo: 'bar', + }, + }; + mockRouter.shouldThrow(request); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/app_search_gated_form.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/app_search_gated_form.ts new file mode 100644 index 00000000000000..47bb3e0c932595 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/app_search_gated_form.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +export function registerAppSearchGatedFormRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.post( + { + path: '/internal/app_search/as_gate', + validate: { + body: schema.object({ + as_gate_data: schema.object({ + additional_feedback: schema.maybe(schema.string()), + feature: schema.string(), + features_other: schema.maybe(schema.string()), + participate_in_ux_labs: schema.maybe(schema.boolean()), + }), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/api/ent/v2/internal/as_gate', + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index c1ce03f33b5879..bc98625bfd5140 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -11,6 +11,7 @@ import { registerCrawlerExtractionRulesRoutes } from '../enterprise_search/crawl import { registerSearchRelevanceSuggestionsRoutes } from './adaptive_relevance'; import { registerAnalyticsRoutes } from './analytics'; import { registerApiLogsRoutes } from './api_logs'; +import { registerAppSearchGatedFormRoute } from './app_search_gated_form'; import { registerCrawlerRoutes } from './crawler'; import { registerCrawlerCrawlRulesRoutes } from './crawler_crawl_rules'; import { registerCrawlerEntryPointRoutes } from './crawler_entry_points'; @@ -54,4 +55,5 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerCrawlerExtractionRulesRoutes(dependencies); registerCrawlerSitemapRoutes(dependencies); registerSearchRelevanceSuggestionsRoutes(dependencies); + registerAppSearchGatedFormRoute(dependencies); }; diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts index 7175f6135e84c1..ef6e3f665b55ac 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts @@ -8,7 +8,7 @@ import { NEVER } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../common/constants'; +import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../common/constants'; import { getSearchResultProvider } from './search_result_provider'; @@ -96,18 +96,6 @@ describe('Enterprise Search search provider', () => { }, }; - const appSearchResult = { - icon: 'logoEnterpriseSearch', - id: 'app_search', - score: 100, - title: 'App Search', - type: 'Search', - url: { - path: `${APP_SEARCH_PLUGIN.URL}`, - prependBasePath: true, - }, - }; - const searchResultProvider = getSearchResultProvider( { hasConnectors: true, @@ -268,7 +256,7 @@ describe('Enterprise Search search provider', () => { }); }); }); - it('returns results for legacy app search', () => { + it('does not return results for legacy app search', () => { const searchProvider = getSearchResultProvider( { canDeployEntSearch: true, @@ -287,7 +275,7 @@ describe('Enterprise Search search provider', () => { {} as any ) ).toBe('(a|)', { - a: [appSearchResult], + a: [], }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts index d4791aa80fe455..bd01e16109f807 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts @@ -16,7 +16,6 @@ import { ConfigType } from '..'; import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, ENTERPRISE_SEARCH_CONTENT_PLUGIN, - APP_SEARCH_PLUGIN, AI_SEARCH_PLUGIN, } from '../../common/constants'; @@ -103,14 +102,6 @@ export function getSearchResultProvider( ...(config.hasConnectors ? connectorTypes : []), ...(config.canDeployEntSearch ? [ - { - keywords: ['app', 'search', 'engines'], - name: i18n.translate('xpack.enterpriseSearch.searchProvider.appSearch.name', { - defaultMessage: 'App Search', - }), - serviceType: 'app_search', - url: APP_SEARCH_PLUGIN.URL, - }, { keywords: ['esre', 'search'], name: i18n.translate('xpack.enterpriseSearch.searchProvider.aiSearch.name', { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index da06991ed2beb2..dd081f3bd2b283 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18079,7 +18079,6 @@ "xpack.enterpriseSearch.searchNav.mngt": "Gestion de la Suite", "xpack.enterpriseSearch.searchNav.relevance": "Pertinence", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "Intelligence artificielle de recherche", - "xpack.enterpriseSearch.searchProvider.appSearch.name": "App Search", "xpack.enterpriseSearch.searchProvider.type.name": "Recherche", "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Robot d'indexation d'Elastic", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "Cliquer pour ouvrir la fenêtre contextuelle d'explication du connecteur", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5d63aaa7da1b13..800729b48217b3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18065,7 +18065,6 @@ "xpack.enterpriseSearch.searchNav.mngt": "スタック管理", "xpack.enterpriseSearch.searchNav.relevance": "関連性", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "検索AI", - "xpack.enterpriseSearch.searchProvider.appSearch.name": "App Search", "xpack.enterpriseSearch.searchProvider.type.name": "検索", "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Elastic Webクローラー", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "クリックすると、コネクター説明ポップオーバーが開きます", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4caba8c275a4b1..ca9ffa4c17f4e6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18091,7 +18091,6 @@ "xpack.enterpriseSearch.searchNav.mngt": "Stack Management", "xpack.enterpriseSearch.searchNav.relevance": "相关性", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "搜索 AI", - "xpack.enterpriseSearch.searchProvider.appSearch.name": "App Search", "xpack.enterpriseSearch.searchProvider.type.name": "搜索", "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Elastic 网络爬虫", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "单击以打开连接器说明弹出框",