diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 5b2e95413af5e..55838b6d880f8 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -44,6 +44,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D const SERVERLESS_ELASTICSEARCH_DOCS = `${SERVERLESS_DOCS}elasticsearch/`; const SERVERLESS_OBSERVABILITY_DOCS = `${SERVERLESS_DOCS}observability/`; const SEARCH_LABS_REPO = `${ELASTIC_GITHUB}elasticsearch-labs/`; + const SEARCH_LABS_BLOG = `${ELASTIC_WEBSITE_URL}search-labs/blog/`; const isServerless = buildFlavor === 'serverless'; return deepFreeze({ @@ -143,6 +144,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D precisionTuning: `${APP_SEARCH_DOCS}precision-tuning.html`, relevanceTuning: `${APP_SEARCH_DOCS}relevance-tuning-guide.html`, resultSettings: `${APP_SEARCH_DOCS}result-settings-guide.html`, + searchLabsEvolutionBlog: `${SEARCH_LABS_BLOG}evolution-app-search-elasticsearch`, searchUI: `${APP_SEARCH_DOCS}reference-ui-guide.html`, security: `${APP_SEARCH_DOCS}security-and-users.html`, synonyms: `${APP_SEARCH_DOCS}synonyms-guide.html`, @@ -258,6 +260,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D permissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-permissions.html`, privateSourcePermissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-permissions.html#organizational-sources-private-sources`, salesforce: `${WORKPLACE_SEARCH_DOCS}workplace-search-salesforce-connector.html`, + searchLabsEvolutionBlog: `${ELASTIC_WEBSITE_URL}blog/evolution-workplace-search-private-data-elasticsearch`, security: `${WORKPLACE_SEARCH_DOCS}workplace-search-security.html`, serviceNow: `${WORKPLACE_SEARCH_DOCS}workplace-search-servicenow-connector.html`, sharePoint: `${WORKPLACE_SEARCH_DOCS}workplace-search-sharepoint-online-connector.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 9870d3687e8b2..703a0f6584584 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -107,6 +107,7 @@ export interface DocLinks { readonly precisionTuning: string; readonly relevanceTuning: string; readonly resultSettings: string; + readonly searchLabsEvolutionBlog: string; readonly searchUI: string; readonly security: string; readonly synonyms: string; @@ -222,6 +223,7 @@ export interface DocLinks { readonly permissions: string; readonly privateSourcePermissions: string; readonly salesforce: string; + readonly searchLabsEvolutionBlog: string; readonly security: string; readonly serviceNow: string; readonly sharePoint: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx index 8fcb57a510a2f..184385d4a1b8a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx @@ -9,9 +9,10 @@ import { setMockValues, mockTelemetryActions } from '../../../../__mocks__/kea_l import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; +import { mount, shallow, ShallowWrapper } from 'enzyme'; import { EuiEmptyPrompt } from '@elastic/eui'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { SampleEngineCreationCta } from '../../sample_engine_creation_cta'; @@ -74,4 +75,42 @@ describe('EmptyState', () => { expect(wrapper.find('[data-test-subj="NonAdminEmptyEnginesPrompt"]')).toHaveLength(1); }); }); + + describe('deprecation callout', () => { + it('renders the deprecation callout when user can manage engines', () => { + setMockValues({ myRole: { canManageEngines: true } }); + const wrapper = shallow(); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + }); + + it('renders the deprecation callout when user cannot manage engines', () => { + setMockValues({ myRole: { canManageEngines: false } }); + const wrapper = shallow(); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + }); + + it('dismisses the deprecation callout', () => { + setMockValues({ myRole: { canManageEngines: false } }); + + const wrapper = mount( + + + + ); + + sessionStorage.setItem('appSearchHideDeprecationCallout', 'false'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + + wrapper.find('button[data-test-subj="euiDismissCalloutButton"]').simulate('click'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0); + expect(sessionStorage.getItem('appSearchHideDeprecationCallout')).toEqual('true'); + }); + + it('does not render the deprecation callout if dismissed', () => { + sessionStorage.setItem('appSearchHideDeprecationCallout', 'true'); + setMockValues({ myRole: { canManageEngines: true } }); + const wrapper = shallow(); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx index df17d22d387d9..1c2b8b7d4e83d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx @@ -12,6 +12,7 @@ import { useValues, useActions } from 'kea'; import { EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { EnterpriseSearchDeprecationCallout } from '../../../../shared/deprecation_callout/deprecation_callout'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../../shared/telemetry'; import { AppLogic } from '../../../app_logic'; @@ -26,66 +27,88 @@ export const EmptyState: React.FC = () => { } = useValues(AppLogic); const { sendAppSearchTelemetry } = useActions(TelemetryLogic); - return canManageEngines ? ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.title', { - defaultMessage: 'Create your first engine', - })} - - } - titleSize="l" - body={ -

- {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.description1', { - defaultMessage: 'An App Search engine stores the documents for your search experience.', - })} -

- } - actions={ - <> - - sendAppSearchTelemetry({ - action: 'clicked', - metric: 'create_first_engine_button', - }) - } - > - {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta', { - defaultMessage: 'Create an engine', - })} - - - - - } - /> - ) : ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title', { - defaultMessage: 'No engines available', - })} - - } - body={ -

- {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description', { - defaultMessage: - 'Contact your App Search administrator to either create or grant you access to an engine.', - })} -

- } - /> + const [showDeprecationCallout, setShowDeprecationCallout] = React.useState( + !sessionStorage.getItem('appSearchHideDeprecationCallout') + ); + + const onDismissDeprecationCallout = () => { + setShowDeprecationCallout(false); + sessionStorage.setItem('appSearchHideDeprecationCallout', 'true'); + }; + + return ( + <> + {showDeprecationCallout ? ( + + ) : ( + <> + )} + {canManageEngines ? ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.title', { + defaultMessage: 'Create your first engine', + })} + + } + titleSize="l" + body={ +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.description1', { + defaultMessage: + 'An App Search engine stores the documents for your search experience.', + })} +

+ } + actions={ + <> + + sendAppSearchTelemetry({ + action: 'clicked', + metric: 'create_first_engine_button', + }) + } + > + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta', + { + defaultMessage: 'Create an engine', + } + )} + + + + + } + /> + ) : ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title', { + defaultMessage: 'No engines available', + })} + + } + body={ +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description', { + defaultMessage: + 'Contact your App Search administrator to either create or grant you access to an engine.', + })} +

+ } + /> + )} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.test.tsx index 2cd48283a2859..5058b066ae3be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.test.tsx @@ -13,9 +13,11 @@ import { setMockValues } from '../../../__mocks__/kea_logic'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import { of } from 'rxjs'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; + import { SetAppSearchChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendAppSearchTelemetry } from '../../../shared/telemetry'; @@ -80,4 +82,32 @@ describe('AppSearchPageTemplate', () => { expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false); expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(
); }); + + describe('deprecation callout', () => { + it('renders the deprecation callout', () => { + const wrapper = shallow(); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + }); + + it('dismisses the deprecation callout', () => { + const wrapper = mount( + + + + ); + + sessionStorage.setItem('appSearchHideDeprecationCallout', 'false'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + + wrapper.find('button[data-test-subj="euiDismissCalloutButton"]').simulate('click'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0); + expect(sessionStorage.getItem('appSearchHideDeprecationCallout')).toEqual('true'); + }); + + it('does not render the deprecation callout if dismissed', () => { + const wrapper = shallow(); + sessionStorage.setItem('appSearchHideDeprecationCallout', 'true'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.tsx index 35792e2d0cbf9..f834c45965508 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.tsx @@ -11,6 +11,7 @@ import { useValues } from 'kea'; import useObservable from 'react-use/lib/useObservable'; import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { EnterpriseSearchDeprecationCallout } from '../../../shared/deprecation_callout/deprecation_callout'; import { KibanaLogic } from '../../../shared/kibana'; import { SetAppSearchChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout'; @@ -36,6 +37,15 @@ export const AppSearchPageTemplate: React.FC< }; }, [chromeStyle, navItems, updateSideNavDefinition]); + const [showDeprecationCallout, setShowDeprecationCallout] = React.useState( + !sessionStorage.getItem('appSearchHideDeprecationCallout') + ); + + const onDismissDeprecationCallout = () => { + setShowDeprecationCallout(false); + sessionStorage.setItem('appSearchHideDeprecationCallout', 'true'); + }; + return ( {pageViewTelemetry && } + {showDeprecationCallout ? ( + + ) : ( + <> + )} {children} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/deprecation_callout/deprecation_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/deprecation_callout/deprecation_callout.test.tsx new file mode 100644 index 0000000000000..e4bad3393e0d9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/deprecation_callout/deprecation_callout.test.tsx @@ -0,0 +1,32 @@ +/* + * 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 } from 'enzyme'; + +import { EnterpriseSearchDeprecationCallout } from './deprecation_callout'; + +describe('EnterpriseSearchDeprecationCallout', () => { + it('renders', () => { + const dismissFxn = jest.fn(); + const wrapper = shallow(); + + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + wrapper.find('EuiCallOut').simulate('dismiss'); + expect(dismissFxn).toHaveBeenCalledTimes(1); + }); + + it('dismisses via the link', () => { + const dismissFxn = jest.fn(); + const wrapper = shallow(); + + expect(wrapper.find('EuiLink')).toHaveLength(1); + wrapper.find('EuiLink').simulate('click'); + expect(dismissFxn).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/deprecation_callout/deprecation_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/deprecation_callout/deprecation_callout.tsx new file mode 100644 index 0000000000000..b9d1157843df1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/deprecation_callout/deprecation_callout.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 { EuiCallOut, EuiButton, EuiLink, EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { docLinks } from '../doc_links'; + +interface DeprecationCalloutProps { + onDismissAction: () => void; +} + +export const EnterpriseSearchDeprecationCallout: React.FC = ({ + onDismissAction, +}) => { + return ( + + + + + {i18n.translate('xpack.enterpriseSearch.deprecationCallout.viewWorkplaceSearchBlog', { + defaultMessage: 'blog post', + })} + + ), + appSearchBlogUrl: ( + + {i18n.translate('xpack.enterpriseSearch.deprecationCallout.viewAppSearchBlog', { + defaultMessage: 'blog post', + })} + + ), + }} + /> + + + + + Learn more + + + + + {i18n.translate('xpack.enterpriseSearch.deprecationCallout.dissmissLink', { + defaultMessage: 'Dismiss', + })} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 311043a442bc8..2c4f7b9e2e5b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -22,6 +22,7 @@ class DocLinks { public appSearchDuplicateDocuments: string; public appSearchElasticsearchIndexedEngines: string; public appSearchEntryPoints: string; + public appSearchEvolutionBlog: string; public appSearchGettingStarted: string; public appSearchGuide: string; public appSearchIndexingDocs: string; @@ -158,6 +159,7 @@ class DocLinks { public workplaceSearchCustomSources: string; public workplaceSearchDocumentPermissions: string; public workplaceSearchDropbox: string; + public workplaceSearchEvolutionBlog: string; public workplaceSearchExternalIdentities: string; public workplaceSearchExternalSharePointOnline: string; public workplaceSearchGatedFormBlog: string; @@ -202,6 +204,7 @@ class DocLinks { this.appSearchDuplicateDocuments = ''; this.appSearchEntryPoints = ''; this.appSearchElasticsearchIndexedEngines = ''; + this.appSearchEvolutionBlog = ''; this.appSearchGettingStarted = ''; this.appSearchGuide = ''; this.appSearchIndexingDocs = ''; @@ -338,6 +341,7 @@ class DocLinks { this.workplaceSearchCustomSourcePermissions = ''; this.workplaceSearchDocumentPermissions = ''; this.workplaceSearchDropbox = ''; + this.workplaceSearchEvolutionBlog = ''; this.workplaceSearchExternalSharePointOnline = ''; this.workplaceSearchExternalIdentities = ''; this.workplaceSearchGatedFormBlog = ''; @@ -384,6 +388,7 @@ class DocLinks { this.appSearchElasticsearchIndexedEngines = docLinks.links.appSearch.elasticsearchIndexedEngines; this.appSearchEntryPoints = docLinks.links.appSearch.entryPoints; + this.appSearchEvolutionBlog = docLinks.links.appSearch.searchLabsEvolutionBlog; this.appSearchGettingStarted = docLinks.links.appSearch.gettingStarted; this.appSearchGuide = docLinks.links.appSearch.guide; this.appSearchIndexingDocs = docLinks.links.appSearch.indexingDocuments; @@ -524,6 +529,7 @@ class DocLinks { docLinks.links.workplaceSearch.customSourcePermissions; this.workplaceSearchDocumentPermissions = docLinks.links.workplaceSearch.documentPermissions; this.workplaceSearchDropbox = docLinks.links.workplaceSearch.dropbox; + this.workplaceSearchEvolutionBlog = docLinks.links.workplaceSearch.searchLabsEvolutionBlog; this.workplaceSearchExternalSharePointOnline = docLinks.links.workplaceSearch.externalSharePointOnline; this.workplaceSearchExternalIdentities = docLinks.links.workplaceSearch.externalIdentities; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.test.tsx index 5b05c00c5a715..5d80e83a484cf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.test.tsx @@ -69,4 +69,40 @@ describe('Overview', () => { expect(wrapper.find(OnboardingSteps)).toHaveLength(0); }); + + describe('deprecation callout', () => { + it('renders the deprecation callout', () => { + setMockValues({ dataLoading: false, organization: { kibanaUIsEnabled: true } }); + const wrapper = shallow(); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + }); + + it('dismisses the deprecation callout', () => { + setMockValues({ dataLoading: false, organization: { kibanaUIsEnabled: true } }); + + const wrapper = shallow(); + + sessionStorage.setItem('workplaceSearchHideDeprecationCallout', 'false'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1); + + wrapper + .find('EnterpriseSearchDeprecationCallout') + .dive() + .find('EuiCallOut') + .simulate('dismiss'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0); + expect(sessionStorage.getItem('workplaceSearchHideDeprecationCallout')).toEqual('true'); + }); + + it('does not render the deprecation callout if dismissed', () => { + setMockValues({ + dataLoading: false, + hideOnboarding: true, + organization: { kibanaUIsEnabled: true }, + }); + const wrapper = shallow(); + sessionStorage.setItem('workplaceSearchHideDeprecationCallout', 'true'); + expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx index a96c16d315ec1..c9c13c3ad5465 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview.tsx @@ -12,6 +12,7 @@ import { useActions, useValues } from 'kea'; import { EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { EnterpriseSearchDeprecationCallout } from '../../../shared/deprecation_callout/deprecation_callout'; import { AppLogic } from '../../app_logic'; import { WorkplaceSearchPageTemplate } from '../../components/layout'; @@ -57,6 +58,15 @@ export const Overview: React.FC = () => { const headerTitle = hideOnboarding ? HEADER_TITLE : ONBOARDING_HEADER_TITLE; const headerDescription = hideOnboarding ? HEADER_DESCRIPTION : ONBOARDING_HEADER_DESCRIPTION; + const [showDeprecationCallout, setShowDeprecationCallout] = React.useState( + !sessionStorage.getItem('workplaceSearchHideDeprecationCallout') + ); + + const onDismissDeprecationCallout = () => { + setShowDeprecationCallout(false); + sessionStorage.setItem('workplaceSearchHideDeprecationCallout', 'true'); + }; + return kibanaUIsEnabled ? ( { pageViewTelemetry="overview" isLoading={dataLoading} > + {showDeprecationCallout ? ( + + ) : ( + <> + )} {!hideOnboarding && ( <>