Skip to content

Commit

Permalink
[Enterprise Search] Adds Callout for Upcoming Decommissioning of App …
Browse files Browse the repository at this point in the history
…Search and Workplace Search (#194363)

## Summary

This PR adds a callout to the overview pages for App Search and
Workplace Search, informing the user of the updating decommissioning of
the Enterprise Search product. Once the user clicks the "Dismiss" link,
or the "x" button (upper-right), the callout will not be shown again
until the user logs back in from a new tab or window. Note that the flag
to show the callout or not is independent of the product (i.e.
dismissing the callout in App Search will still show the callout in
Workplace Search, until that one is dismissed as well).

(Note that the link provided to the _App Search_ (catalog) blog post
_is_ the correct URL, but the actual post will be forthcoming before the
release)


![image](https://github.com/user-attachments/assets/57b41839-ca44-4bbc-af27-b8b7e65298f6)

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
markjhoy and kibanamachine authored Oct 10, 2024
1 parent e435c47 commit 6910f15
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 63 deletions.
3 changes: 3 additions & 0 deletions packages/kbn-doc-links/src/get_doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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`,
Expand Down Expand Up @@ -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`,
Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(<EmptyState />);
expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1);
});

it('renders the deprecation callout when user cannot manage engines', () => {
setMockValues({ myRole: { canManageEngines: false } });
const wrapper = shallow(<EmptyState />);
expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1);
});

it('dismisses the deprecation callout', () => {
setMockValues({ myRole: { canManageEngines: false } });

const wrapper = mount(
<IntlProvider locale="en">
<EmptyState />
</IntlProvider>
);

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(<EmptyState />);
expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,66 +27,88 @@ export const EmptyState: React.FC = () => {
} = useValues(AppLogic);
const { sendAppSearchTelemetry } = useActions(TelemetryLogic);

return canManageEngines ? (
<EuiEmptyPrompt
data-test-subj="AdminEmptyEnginesPrompt"
iconType={EngineIcon}
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.title', {
defaultMessage: 'Create your first engine',
})}
</h2>
}
titleSize="l"
body={
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.description1', {
defaultMessage: 'An App Search engine stores the documents for your search experience.',
})}
</p>
}
actions={
<>
<EuiButtonTo
data-test-subj="EmptyStateCreateFirstEngineCta"
fill
to={ENGINE_CREATION_PATH}
onClick={() =>
sendAppSearchTelemetry({
action: 'clicked',
metric: 'create_first_engine_button',
})
}
>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta', {
defaultMessage: 'Create an engine',
})}
</EuiButtonTo>
<EuiSpacer size="xxl" />
<SampleEngineCreationCta />
</>
}
/>
) : (
<EuiEmptyPrompt
data-test-subj="NonAdminEmptyEnginesPrompt"
iconType={EngineIcon}
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title', {
defaultMessage: 'No engines available',
})}
</h2>
}
body={
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description', {
defaultMessage:
'Contact your App Search administrator to either create or grant you access to an engine.',
})}
</p>
}
/>
const [showDeprecationCallout, setShowDeprecationCallout] = React.useState(
!sessionStorage.getItem('appSearchHideDeprecationCallout')
);

const onDismissDeprecationCallout = () => {
setShowDeprecationCallout(false);
sessionStorage.setItem('appSearchHideDeprecationCallout', 'true');
};

return (
<>
{showDeprecationCallout ? (
<EnterpriseSearchDeprecationCallout onDismissAction={onDismissDeprecationCallout} />
) : (
<></>
)}
{canManageEngines ? (
<EuiEmptyPrompt
data-test-subj="AdminEmptyEnginesPrompt"
iconType={EngineIcon}
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.title', {
defaultMessage: 'Create your first engine',
})}
</h2>
}
titleSize="l"
body={
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.description1', {
defaultMessage:
'An App Search engine stores the documents for your search experience.',
})}
</p>
}
actions={
<>
<EuiButtonTo
data-test-subj="EmptyStateCreateFirstEngineCta"
fill
to={ENGINE_CREATION_PATH}
onClick={() =>
sendAppSearchTelemetry({
action: 'clicked',
metric: 'create_first_engine_button',
})
}
>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta',
{
defaultMessage: 'Create an engine',
}
)}
</EuiButtonTo>
<EuiSpacer size="xxl" />
<SampleEngineCreationCta />
</>
}
/>
) : (
<EuiEmptyPrompt
data-test-subj="NonAdminEmptyEnginesPrompt"
iconType={EngineIcon}
title={
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title', {
defaultMessage: 'No engines available',
})}
</h2>
}
body={
<p>
{i18n.translate('xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description', {
defaultMessage:
'Contact your App Search administrator to either create or grant you access to an engine.',
})}
</p>
}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -80,4 +82,32 @@ describe('AppSearchPageTemplate', () => {
expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false);
expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(<div />);
});

describe('deprecation callout', () => {
it('renders the deprecation callout', () => {
const wrapper = shallow(<AppSearchPageTemplate />);
expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(1);
});

it('dismisses the deprecation callout', () => {
const wrapper = mount(
<IntlProvider locale="en">
<AppSearchPageTemplate />
</IntlProvider>
);

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(<AppSearchPageTemplate />);
sessionStorage.setItem('appSearchHideDeprecationCallout', 'true');
expect(wrapper.find('EnterpriseSearchDeprecationCallout')).toHaveLength(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 (
<EnterpriseSearchPageTemplateWrapper
{...pageTemplateProps}
Expand All @@ -48,6 +58,11 @@ export const AppSearchPageTemplate: React.FC<
hideEmbeddedConsole
>
{pageViewTelemetry && <SendAppSearchTelemetry action="viewed" metric={pageViewTelemetry} />}
{showDeprecationCallout ? (
<EnterpriseSearchDeprecationCallout onDismissAction={onDismissDeprecationCallout} />
) : (
<></>
)}
{children}
</EnterpriseSearchPageTemplateWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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(<EnterpriseSearchDeprecationCallout onDismissAction={dismissFxn} />);

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(<EnterpriseSearchDeprecationCallout onDismissAction={dismissFxn} />);

expect(wrapper.find('EuiLink')).toHaveLength(1);
wrapper.find('EuiLink').simulate('click');
expect(dismissFxn).toHaveBeenCalledTimes(1);
});
});
Loading

0 comments on commit 6910f15

Please sign in to comment.