mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Search] Add Legacy App Search Gated Form (#189816)
## Summary This PR adds Gated form and logic when user visits App Search , when `kibana_uis_enabled == false` and the `role_type` is `owner`. The user will not be able to able to access any other App Search routes other than Engines Overview page until this form is submitted. Also - removes App Search from the search providers so it will no longer show up in the results. (note - any migrated clusters will automatically allow access without the gate on the backend - that is, it will already have set `kibana_uis_enabled=true`) Also updates a couple of links and CTA for Workplace Search to be consistent with new App Search ones. **Screen Recording:** https://github.com/user-attachments/assets/8e3d73b3-52d4-4952-bb1e-49219c8cdaed ### Checklist Delete any items that are not applicable to this PR. - [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] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [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: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f98ef4c04a
commit
845d275d3c
26 changed files with 1099 additions and 40 deletions
|
@ -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',
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
export interface Account {
|
||||
accountId: string;
|
||||
kibanaUIsEnabled: boolean;
|
||||
onboardingComplete: boolean;
|
||||
role: {
|
||||
id: string;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<MakeLogicType<AppValues, AppActions, Required<Initia
|
|||
},
|
||||
],
|
||||
configuredLimits: [props.configuredLimits.appSearch, {}],
|
||||
showGateForm: [
|
||||
props.appSearch.kibanaUIsEnabled === false && props.appSearch.role.roleType === 'owner',
|
||||
{},
|
||||
],
|
||||
}),
|
||||
selectors: {
|
||||
myRole: [
|
||||
|
|
|
@ -0,0 +1,613 @@
|
|||
/*
|
||||
* 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 { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormLabel,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSelect,
|
||||
EuiSpacer,
|
||||
EuiSuperSelect,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { docLinks } from '../../../shared/doc_links';
|
||||
|
||||
import { AppSearchGateLogic } from './app_search_gate_logic';
|
||||
|
||||
const featuresList = {
|
||||
webCrawler: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Try Open Crawler',
|
||||
}
|
||||
),
|
||||
actionLink: 'https://github.com/elastic/crawler?tab=readme-ov-file#setup',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureDescription',
|
||||
{
|
||||
defaultMessage: 'Ingest web content into Elasticsearch using a web crawler',
|
||||
}
|
||||
),
|
||||
id: 'webCrawler',
|
||||
learnMore: 'https://github.com/elastic/crawler#readme ',
|
||||
panelText: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.webCrawler.panelText', {
|
||||
defaultMessage:
|
||||
'Did you know the new self-managed Elastic open crawler is now available? You can keep your web content in sync with your search-optimized indices!',
|
||||
}),
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureName', {
|
||||
defaultMessage: 'Web crawler',
|
||||
}),
|
||||
},
|
||||
analyticsAndLogs: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Add search analytics',
|
||||
}
|
||||
),
|
||||
actionLink:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-event.html',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureDescription',
|
||||
{
|
||||
defaultMessage: 'Add and view analytics and logs for your search application',
|
||||
}
|
||||
),
|
||||
id: 'analyticsAndLogs',
|
||||
learnMore:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-overview.html',
|
||||
panelText: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.panelText',
|
||||
{
|
||||
defaultMessage:
|
||||
"You can track and analyze users' searching and clicking behavior with Behavioral Analytics. Instrument your website or application to track relevant user actions.",
|
||||
}
|
||||
),
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureName',
|
||||
{
|
||||
defaultMessage: 'Search analytics and logs',
|
||||
}
|
||||
),
|
||||
},
|
||||
synonyms: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Search with synonyms',
|
||||
}
|
||||
),
|
||||
actionLink:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/synonyms-apis.html',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureDescription',
|
||||
{
|
||||
defaultMessage: 'Perform search with synonym based query expansion',
|
||||
}
|
||||
),
|
||||
id: 'synonyms',
|
||||
learnMore:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-with-synonyms.html',
|
||||
panelText: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.synonyms.panelText', {
|
||||
defaultMessage:
|
||||
'Use the Elasticsearch Synonyms APIs to easily create and manage synonym sets.',
|
||||
}),
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureName', {
|
||||
defaultMessage: 'Search with synonyms',
|
||||
}),
|
||||
},
|
||||
relevanceTuning: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Tune search relevancy',
|
||||
}
|
||||
),
|
||||
actionLink:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-with-elasticsearch.html',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureDescription',
|
||||
{
|
||||
defaultMessage: 'Tune the relevancy of your results using ranking and boosting methods',
|
||||
}
|
||||
),
|
||||
id: 'relevanceTuning',
|
||||
learnMore:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html',
|
||||
panelText: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.panelText',
|
||||
{
|
||||
defaultMessage: "Elasticsearch's query DSL provides an in-depth set of relevance tools.",
|
||||
}
|
||||
),
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureName', {
|
||||
defaultMessage: 'Relevance tuning',
|
||||
}),
|
||||
},
|
||||
curations: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.curations.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Use query rules',
|
||||
}
|
||||
),
|
||||
actionLink:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/search-using-query-rules.html',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.curations.featureDescription',
|
||||
{
|
||||
defaultMessage: 'Curate and pin results for specific queries',
|
||||
}
|
||||
),
|
||||
id: 'curations',
|
||||
learnMore: 'https://www.elastic.co/blog/introducing-query-rules-elasticsearch-8-10',
|
||||
panelText: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.curations.panelText', {
|
||||
defaultMessage:
|
||||
'Query rules provide a more robust set of tools to customize your search results for queries that match specific criteria and metadata.',
|
||||
}),
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.curations.featureName', {
|
||||
defaultMessage: 'Curate results',
|
||||
}),
|
||||
},
|
||||
searchManagementUis: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Build a search experience with Search UI',
|
||||
}
|
||||
),
|
||||
actionLink: 'https://www.elastic.co/docs/current/search-ui/overview',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Use graphical user interfaces (GUIs) to manage your search application experience',
|
||||
}
|
||||
),
|
||||
id: 'searchManagementUis',
|
||||
learnMore: 'https://www.elastic.co/docs/current/search-ui/tutorials/elasticsearch',
|
||||
panelText: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.panelText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Search UI provides the components needed to build a modern search experience.',
|
||||
}
|
||||
),
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureName',
|
||||
{
|
||||
defaultMessage: 'Search and management UIs',
|
||||
}
|
||||
),
|
||||
},
|
||||
credentials: {
|
||||
actionLabel: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.credentials.featureButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Secure with Elasticsearch',
|
||||
}
|
||||
),
|
||||
actionLink:
|
||||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/document-level-security.html',
|
||||
addOnLearnMoreLabel: undefined,
|
||||
addOnLearnMoreUrl: undefined,
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.credentials.featureDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Manage your users and roles, and credentials for accessing your search endpoints',
|
||||
}
|
||||
),
|
||||
id: 'credentials',
|
||||
learnMore: 'https://www.elastic.co/search-labs/blog/dls-internal-knowledge-search',
|
||||
panelText: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.credentials.panelText', {
|
||||
defaultMessage:
|
||||
'Elasticsearch provides a comprehensive set of security features, including document-level security and role-based access control.',
|
||||
}),
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.credentials.featureName', {
|
||||
defaultMessage: 'Credentials and roles',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
interface FeatureOption {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
learnMore: string | undefined;
|
||||
actionLabel: string;
|
||||
actionLink: string;
|
||||
panelText: string;
|
||||
addOnLearnMoreLabel?: string;
|
||||
addOnLearnMoreUrl?: string;
|
||||
}
|
||||
|
||||
const getFeature = (id: string): FeatureOption | undefined => {
|
||||
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: (
|
||||
<>
|
||||
<strong>{feature.title}</strong>
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>{feature.description}</p>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
inputDisplay: feature.title,
|
||||
value: feature.id,
|
||||
};
|
||||
});
|
||||
|
||||
featureList.push({
|
||||
dropdownDisplay: (
|
||||
<>
|
||||
<strong>
|
||||
{i18n.translate(`${baseTranslatePrefix}.other.title`, {
|
||||
defaultMessage: 'Other',
|
||||
})}
|
||||
</strong>
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>
|
||||
{i18n.translate(`${baseTranslatePrefix}.other.description`, {
|
||||
defaultMessage: 'Another feature not listed here',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
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 (
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="logoElastic" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h5>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.title',
|
||||
{
|
||||
defaultMessage: 'Elasticsearch native equivalent',
|
||||
}
|
||||
)}
|
||||
</h5>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.subTitle',
|
||||
{
|
||||
defaultMessage: 'Based on your selection we recommend:',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiCallOut title={feature.title} color="success" iconType="checkInCircleFilled">
|
||||
<p>{feature.panelText}</p>
|
||||
<EuiFlexGroup gutterSize="m" wrap alignItems="baseline">
|
||||
{feature.actionLink !== undefined && feature.actionLabel !== undefined && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
href={feature.actionLink}
|
||||
target="_blank"
|
||||
iconType="sortRight"
|
||||
iconSide="right"
|
||||
>
|
||||
{feature.actionLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{feature.learnMore !== undefined && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href={feature.learnMore} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.learnMore',
|
||||
{
|
||||
defaultMessage: 'Learn more',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{feature.addOnLearnMoreLabel !== undefined &&
|
||||
feature.addOnLearnMoreUrl !== undefined && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink type="button" href={feature.addOnLearnMoreUrl} target="_blank" external>
|
||||
<EuiSpacer />
|
||||
{feature.addOnLearnMoreLabel}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiCallOut>
|
||||
</EuiPanel>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.featureOther.Label', {
|
||||
defaultMessage: "Can you explain what other feature(s) you're looking for?",
|
||||
})}
|
||||
>
|
||||
<EuiTextArea
|
||||
onChange={(e) => {
|
||||
setFeaturesOther(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const AppSearchGate: React.FC = () => {
|
||||
const { feature, participateInUXLabs } = useValues(AppSearchGateLogic);
|
||||
const { formSubmitRequest, setAdditionalFeedback, setParticipateInUXLabs, setFeature } =
|
||||
useActions(AppSearchGateLogic);
|
||||
const options = getOptionsFeaturesList();
|
||||
return (
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiForm component="form" fullWidth>
|
||||
<EuiFormLabel>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.features.Label', {
|
||||
defaultMessage: 'What App Search feature are you looking to use?',
|
||||
})}
|
||||
</EuiFormLabel>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiSuperSelect
|
||||
options={options}
|
||||
valueOfSelected={feature}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.features.selectOption',
|
||||
{
|
||||
defaultMessage: 'Select an option',
|
||||
}
|
||||
)}
|
||||
onChange={(value) => setFeature(value)}
|
||||
itemLayoutAlign="top"
|
||||
hasDividers
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
{feature && <EducationPanel featureContent={feature} />}
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.Label',
|
||||
{
|
||||
defaultMessage: 'Would you like to share any additional feedback?',
|
||||
}
|
||||
)}
|
||||
labelAppend={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.optionalLabel',
|
||||
{
|
||||
defaultMessage: 'Optional',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiTextArea
|
||||
onChange={(e) => {
|
||||
setAdditionalFeedback(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="xs">
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.description"
|
||||
defaultMessage=" By submitting feedback you acknowledge that you've read and agree to our {termsOfService}, and that Elastic may {contact} about our related products and services,
|
||||
using the details you provide above. See {privacyStatementLink} for more
|
||||
details or to opt-out at any time."
|
||||
values={{
|
||||
contact: (
|
||||
<EuiLink href={docLinks.workplaceSearchGatedFormDataUse}>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.contact"
|
||||
defaultMessage="contact you"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
privacyStatementLink: (
|
||||
<EuiLink href={docLinks.workplaceSearchGatedFormPrivacyStatement}>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readDataPrivacyStatementLink"
|
||||
defaultMessage="Elastic’s Privacy Statement"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
termsOfService: (
|
||||
<EuiLink href={docLinks.workplaceSearchGatedFormTermsOfService}>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readTermsOfService"
|
||||
defaultMessage="Terms of Service"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFormRow
|
||||
labelAppend={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.optionalLabel',
|
||||
{
|
||||
defaultMessage: 'Optional',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label',
|
||||
{
|
||||
defaultMessage: 'Join our user research studies to improve Elasticsearch?',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiSelect
|
||||
hasNoInitialSelection
|
||||
options={[
|
||||
{
|
||||
text: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.Yes',
|
||||
{
|
||||
defaultMessage: 'Yes',
|
||||
}
|
||||
),
|
||||
value: participateInUXLabsChoice.yes.choice,
|
||||
},
|
||||
{
|
||||
text: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.No',
|
||||
{
|
||||
defaultMessage: 'No',
|
||||
}
|
||||
),
|
||||
value: participateInUXLabsChoice.no.choice,
|
||||
},
|
||||
]}
|
||||
onChange={(e) =>
|
||||
setParticipateInUXLabs(
|
||||
e.target.value === participateInUXLabsChoice.yes.choice
|
||||
? participateInUXLabsChoice.yes.value
|
||||
: participateInUXLabsChoice.no.value
|
||||
)
|
||||
}
|
||||
value={
|
||||
participateInUXLabs !== null
|
||||
? participateInUXLabs
|
||||
? participateInUXLabsChoice.yes.choice
|
||||
: participateInUXLabsChoice.no.choice
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isDisabled={!feature ?? false}
|
||||
type="submit"
|
||||
fill
|
||||
onClick={() => formSubmitRequest()}
|
||||
>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.submit', {
|
||||
defaultMessage: 'Submit',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -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"}}',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<AppSearchGatedFormDataApiLogicResponse> => {
|
||||
return await HttpLogic.values.http.post<AppSearchGatedFormDataApiLogicResponse>(
|
||||
'/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
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<MakeLogicType<AppSearchGateValues, AppSearchGateActions>>({
|
||||
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,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -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<PageTemplateProps> = ({ isLoading }) => {
|
||||
return (
|
||||
<EnterpriseSearchPageTemplateWrapper
|
||||
restrictWidth
|
||||
pageHeader={{
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.gateForm.description"
|
||||
defaultMessage="The standalone App Search product remains available in maintenance mode, but is not recommended for new search experiences. We recommend using native Elasticsearch tools, which offer flexibility and composability, and include exciting new search features. To help you choose the tools best suited for your use case, we’ve created this recommendation wizard. Select the features you need, and we'll point you to corresponding Elasticsearch features. If you still want to use the standalone App Search product, you can do so after submitting the form."
|
||||
/>
|
||||
),
|
||||
pageTitle: i18n.translate('xpack.enterpriseSearch.appSearch.gateForm.title', {
|
||||
defaultMessage: 'Before you begin...',
|
||||
}),
|
||||
}}
|
||||
solutionNav={{
|
||||
items: useEnterpriseSearchNav(),
|
||||
name: ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAME,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
hideEmbeddedConsole
|
||||
>
|
||||
<SendAppSearchTelemetry action="viewed" metric="App Search Gate form" />
|
||||
|
||||
<AppSearchGate />
|
||||
</EnterpriseSearchPageTemplateWrapper>
|
||||
);
|
||||
};
|
|
@ -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';
|
|
@ -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(<EnginesOverview />);
|
||||
|
||||
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(<EnginesOverview />);
|
||||
|
||||
expect(wrapper.find(EnginesTable)).toHaveLength(1);
|
||||
expect(wrapper.find(AppSearchGatePage)).toHaveLength(0);
|
||||
expect(actions.loadEngines).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -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 ? (
|
||||
<AppSearchPageTemplate
|
||||
pageViewTelemetry="engines_overview"
|
||||
pageChrome={[ENGINES_TITLE]}
|
||||
|
@ -131,5 +134,7 @@ export const EnginesOverview: React.FC = () => {
|
|||
</DataPanel>
|
||||
<AuditLogsModal />
|
||||
</AppSearchPageTemplate>
|
||||
) : (
|
||||
<AppSearchGatePage isLoading={dataLoading} />
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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(<AppSearchConfigured {...DEFAULT_INITIAL_APP_DATA} />);
|
||||
});
|
||||
|
||||
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();
|
||||
|
|
|
@ -93,6 +93,7 @@ export const AppSearchUnconfigured: React.FC = () => {
|
|||
|
||||
export const AppSearchConfigured: React.FC<Required<InitialAppData>> = (props) => {
|
||||
const {
|
||||
showGateForm,
|
||||
myRole: {
|
||||
canManageEngines,
|
||||
canManageMetaEngines,
|
||||
|
@ -107,7 +108,7 @@ export const AppSearchConfigured: React.FC<Required<InitialAppData>> = (props) =
|
|||
renderHeaderActions(KibanaHeaderActions);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
return !showGateForm ? (
|
||||
<Routes>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<Route path={LIBRARY_PATH}>
|
||||
|
@ -152,5 +153,17 @@ export const AppSearchConfigured: React.FC<Required<InitialAppData>> = (props) =
|
|||
<NotFound />
|
||||
</Route>
|
||||
</Routes>
|
||||
) : (
|
||||
<Routes>
|
||||
<Route exact path={ROOT_PATH}>
|
||||
<Redirect to={ENGINES_PATH} />
|
||||
</Route>
|
||||
<Route exact path={ENGINES_PATH}>
|
||||
<EnginesOverview />
|
||||
</Route>
|
||||
<Route>
|
||||
<NotFound />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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:',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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',
|
||||
})
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -18065,7 +18065,6 @@
|
|||
"xpack.enterpriseSearch.searchNav.mngt": "スタック管理",
|
||||
"xpack.enterpriseSearch.searchNav.relevance": "<b>関連性</b>",
|
||||
"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": "クリックすると、コネクター説明ポップオーバーが開きます",
|
||||
|
|
|
@ -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": "单击以打开连接器说明弹出框",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue