diff --git a/examples/guided_onboarding_example/public/components/main.tsx b/examples/guided_onboarding_example/public/components/main.tsx index bf409520d8833..d172cfbb0c8f3 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -37,7 +37,6 @@ interface MainProps { } const exampleGuideIds: GuideId[] = [ - 'appSearch', 'websiteSearch', 'databaseSearch', 'siem', diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts index f4a5af26c4176..064b77a62baaf 100644 --- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts +++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts @@ -26,8 +26,6 @@ import type { EnterpriseSearchContentApp, EnterpriseSearchApplicationsApp, EnterpriseSearchAnalyticsApp, - EnterpriseSearchAppsearchApp, - EnterpriseSearchWorkplaceSearchApp, ServerlessSearchApp, DeepLinkId as SearchLink, } from '@kbn/deeplinks-search'; @@ -54,8 +52,6 @@ export type AppId = | EnterpriseSearchContentApp | EnterpriseSearchApplicationsApp | EnterpriseSearchAnalyticsApp - | EnterpriseSearchAppsearchApp - | EnterpriseSearchWorkplaceSearchApp | ServerlessSearchApp | ObservabilityApp | SecurityApp diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts index cd632c5b24a03..f3453f731ffb3 100644 --- a/packages/deeplinks/search/constants.ts +++ b/packages/deeplinks/search/constants.ts @@ -11,8 +11,6 @@ export const ENTERPRISE_SEARCH_APP_ID = 'enterpriseSearch'; export const ENTERPRISE_SEARCH_CONTENT_APP_ID = 'enterpriseSearchContent'; export const ENTERPRISE_SEARCH_APPLICATIONS_APP_ID = 'enterpriseSearchApplications'; export const ENTERPRISE_SEARCH_ANALYTICS_APP_ID = 'enterpriseSearchAnalytics'; -export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch'; -export const ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID = 'workplaceSearch'; export const SERVERLESS_ES_APP_ID = 'serverlessElasticsearch'; export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors'; export const SERVERLESS_ES_WEB_CRAWLERS_ID = 'serverlessWebCrawlers'; diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts index dcba1f7888597..d76ab9425a5df 100644 --- a/packages/deeplinks/search/deep_links.ts +++ b/packages/deeplinks/search/deep_links.ts @@ -15,8 +15,6 @@ import { ENTERPRISE_SEARCH_CONTENT_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, - ENTERPRISE_SEARCH_APPSEARCH_APP_ID, - ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID, ES_SEARCH_PLAYGROUND_ID, SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID, SEARCH_HOMEPAGE, @@ -33,8 +31,6 @@ export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID; export type EnterpriseSearchContentApp = typeof ENTERPRISE_SEARCH_CONTENT_APP_ID; export type EnterpriseSearchApplicationsApp = typeof ENTERPRISE_SEARCH_APPLICATIONS_APP_ID; export type EnterpriseSearchAnalyticsApp = typeof ENTERPRISE_SEARCH_ANALYTICS_APP_ID; -export type EnterpriseSearchAppsearchApp = typeof ENTERPRISE_SEARCH_APPSEARCH_APP_ID; -export type EnterpriseSearchWorkplaceSearchApp = typeof ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID; export type ServerlessSearchApp = typeof SERVERLESS_ES_APP_ID; export type ConnectorsId = typeof SERVERLESS_ES_CONNECTORS_ID; export type ServerlessWebCrawlers = typeof SERVERLESS_ES_WEB_CRAWLERS_ID; @@ -52,8 +48,6 @@ export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers'; export type ApplicationsLinkId = 'searchApplications'; -export type AppsearchLinkId = 'engines'; - export type SearchInferenceEndpointsLinkId = 'inferenceEndpoints'; export type SearchIndicesLinkId = typeof SEARCH_INDICES_CREATE_INDEX; @@ -63,8 +57,6 @@ export type DeepLinkId = | EnterpriseSearchContentApp | EnterpriseSearchApplicationsApp | EnterpriseSearchAnalyticsApp - | EnterpriseSearchAppsearchApp - | EnterpriseSearchWorkplaceSearchApp | ServerlessSearchApp | ConnectorsId | ServerlessWebCrawlers @@ -73,7 +65,6 @@ export type DeepLinkId = | SearchHomepage | `${EnterpriseSearchContentApp}:${ContentLinkId}` | `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}` - | `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}` | `${SearchInferenceEndpointsId}:${SearchInferenceEndpointsLinkId}` | SearchStart | SearchIndices diff --git a/packages/deeplinks/search/index.ts b/packages/deeplinks/search/index.ts index 2b3d392971a5f..ab9d2e6a8d43f 100644 --- a/packages/deeplinks/search/index.ts +++ b/packages/deeplinks/search/index.ts @@ -12,8 +12,6 @@ export { ENTERPRISE_SEARCH_CONTENT_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, - ENTERPRISE_SEARCH_APPSEARCH_APP_ID, - ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID, SERVERLESS_ES_APP_ID, SERVERLESS_ES_CONNECTORS_ID, SEARCH_ELASTICSEARCH, @@ -28,8 +26,6 @@ export type { EnterpriseSearchContentApp, EnterpriseSearchApplicationsApp, EnterpriseSearchAnalyticsApp, - EnterpriseSearchAppsearchApp, - EnterpriseSearchWorkplaceSearchApp, ServerlessSearchApp, DeepLinkId, } from './deep_links'; diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 663fef8f4fef1..68f88a66bb9cb 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -332,10 +332,6 @@ "dynamic": false, "properties": {} }, - "app_search_telemetry": { - "dynamic": false, - "properties": {} - }, "application_usage_daily": { "dynamic": false, "properties": { @@ -3772,9 +3768,5 @@ "type": "integer" } } - }, - "workplace_search_telemetry": { - "dynamic": false, - "properties": {} } } diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_cards.constants.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_cards.constants.tsx index f7b3a72052537..2c60bd7e377fe 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_cards.constants.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_cards.constants.tsx @@ -72,22 +72,6 @@ export const guideCards: GuideCardConstants[] = [ telemetryId: 'onboarding--search--ai', order: 7, }, - { - solution: 'search', - icon: 'wrench', - title: ( - , - }} - /> - ), - guideId: 'appSearch', - telemetryId: 'onboarding--search--application', - order: 10, - }, { solution: 'search', icon: 'search', diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_cards.constants.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_cards.constants.tsx index 4728b70c338aa..ed52a70dfdc4e 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_cards.constants.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_cards.constants.tsx @@ -84,22 +84,6 @@ export const guideCards: GuideCardConstants[] = [ telemetryId: 'onboarding--search--ai', order: 7, }, - { - solution: 'search', - icon: 'wrench', - title: ( - , - }} - /> - ), - guideId: 'appSearch', - telemetryId: 'onboarding--search--application', - order: 10, - }, { solution: 'search', icon: 'search', diff --git a/packages/kbn-guided-onboarding/src/types.ts b/packages/kbn-guided-onboarding/src/types.ts index 9d53f30aed209..f9f1cbc2053bd 100644 --- a/packages/kbn-guided-onboarding/src/types.ts +++ b/packages/kbn-guided-onboarding/src/types.ts @@ -7,13 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export type GuideId = - | 'kubernetes' - | 'siem' - | 'appSearch' - | 'websiteSearch' - | 'databaseSearch' - | 'testGuide'; +export type GuideId = 'kubernetes' | 'siem' | 'websiteSearch' | 'databaseSearch' | 'testGuide'; type KubernetesStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; type SiemStepIds = 'add_data' | 'rules' | 'alertsCases'; diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index b2d9693cfcc22..728cd6612811e 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -66,7 +66,6 @@ describe('checking migration metadata changes on all registered SO types', () => "apm-server-schema": "58a8c6468edae3d1dc520f0134f59cf3f4fd7eff", "apm-service-group": "66dfc1ddd40bad8f693c873bf6002ca30079a4ae", "apm-telemetry": "4df255b8b022f5d160687db736b9abcd6ab173fe", - "app_search_telemetry": "36234f19573ad397ac30197c45ac219921cc3106", "application_usage_daily": "20142d23fe5d05ba22b4bc46614d99883bc488f0", "application_usage_totals": "a29ab014edc20382b9ce22ede221b18cee5d93a6", "background-task-node": "e61f0ea9923fa05b3af0aae6c6baf2f0283e14b3", @@ -182,7 +181,6 @@ describe('checking migration metadata changes on all registered SO types', () => "usage-counter": "1690e9b642393c467e560fd14dd317dea24a14ee", "usage-counters": "48782b3bcb6b5a23ba6f2bfe3a380d835e68890a", "visualization": "93a3e73994ad836fe2b1dccbe208238f41f63da0", - "workplace_search_telemetry": "52b32b47ee576f554ac77cb1d5896dfbcfe9a1fb", } `); }); diff --git a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts index b79fab50089f4..355c2b4bbca81 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts @@ -34,10 +34,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D const OBSERVABILITY_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/`; const APM_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/apm/`; const SECURITY_SOLUTION_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/`; - const APP_SEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/app-search/${DOC_LINK_VERSION}/`; const ENTERPRISE_SEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/enterprise-search/${DOC_LINK_VERSION}/`; const ESRE_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/esre/${DOC_LINK_VERSION}/`; - const WORKPLACE_SEARCH_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/workplace-search/${DOC_LINK_VERSION}/`; const SEARCH_UI_DOCS = `${DOCS_WEBSITE_URL}search-ui/`; const MACHINE_LEARNING_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/`; const SERVERLESS_DOCS = `${ELASTIC_WEBSITE_URL}guide/en/serverless/current/`; @@ -121,33 +119,6 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D auditdModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-auditd.html`, systemModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-system.html`, }, - appSearch: { - adaptiveRelevance: `${APP_SEARCH_DOCS}curations-guide.html#curations-reference-adaptive-relevance`, - apiRef: `${APP_SEARCH_DOCS}api-reference.html`, - apiClients: `${APP_SEARCH_DOCS}api-clients.html`, - apiKeys: `${APP_SEARCH_DOCS}authentication.html#authentication-api-keys`, - authentication: `${APP_SEARCH_DOCS}authentication.html`, - crawlRules: `${APP_SEARCH_DOCS}crawl-web-content.html#crawl-web-content-manage-crawl-rules`, - curations: `${APP_SEARCH_DOCS}curations-guide.html`, - duplicateDocuments: `${APP_SEARCH_DOCS}web-crawler-reference.html#web-crawler-reference-content-deduplication`, - elasticsearchIndexedEngines: `${APP_SEARCH_DOCS}elasticsearch-engines.html`, - entryPoints: `${APP_SEARCH_DOCS}crawl-web-content.html#crawl-web-content-manage-entry-points`, - gettingStarted: `${APP_SEARCH_DOCS}getting-started.html`, - guide: `${APP_SEARCH_DOCS}index.html`, - indexingDocuments: `${APP_SEARCH_DOCS}indexing-documents-guide.html`, - indexingDocumentsSchema: `${APP_SEARCH_DOCS}indexing-documents-guide.html#indexing-documents-guide-schema`, - logSettings: `${APP_SEARCH_DOCS}logs.html`, - metaEngines: `${APP_SEARCH_DOCS}meta-engines-guide.html`, - precisionTuning: `${APP_SEARCH_DOCS}precision-tuning.html`, - relevanceTuning: `${APP_SEARCH_DOCS}relevance-tuning-guide.html`, - resultSettings: `${APP_SEARCH_DOCS}result-settings-guide.html`, - searchUI: `${APP_SEARCH_DOCS}reference-ui-guide.html`, - security: `${APP_SEARCH_DOCS}security-and-users.html`, - synonyms: `${APP_SEARCH_DOCS}synonyms-guide.html`, - webCrawler: `${APP_SEARCH_DOCS}web-crawler.html`, - webCrawlerEventLogs: `${APP_SEARCH_DOCS}view-web-crawler-events-logs.html`, - webCrawlerReference: `${APP_SEARCH_DOCS}web-crawler-reference.html`, - }, enterpriseSearch: { aiSearchDoc: `${ESRE_DOCS}`, aiSearchHelp: `${ESRE_DOCS}help.html`, @@ -229,41 +200,6 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D troubleshootSetup: `${ENTERPRISE_SEARCH_DOCS}troubleshoot-setup.html`, usersAccess: `${ENTERPRISE_SEARCH_DOCS}users-access.html`, }, - workplaceSearch: { - apiKeys: `${WORKPLACE_SEARCH_DOCS}workplace-search-api-authentication.html`, - box: `${WORKPLACE_SEARCH_DOCS}workplace-search-box-connector.html`, - confluenceCloud: `${WORKPLACE_SEARCH_DOCS}workplace-search-confluence-cloud-connector.html`, - confluenceCloudConnectorPackage: `${WORKPLACE_SEARCH_DOCS}confluence-cloud.html`, - confluenceServer: `${WORKPLACE_SEARCH_DOCS}workplace-search-confluence-server-connector.html`, - contentSources: `${WORKPLACE_SEARCH_DOCS}workplace-search-content-sources.html`, - customConnectorPackage: `${WORKPLACE_SEARCH_DOCS}custom-connector-package.html`, - customSources: `${WORKPLACE_SEARCH_DOCS}workplace-search-custom-api-sources.html`, - customSourcePermissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-custom-api-sources.html#custom-api-source-document-level-access-control`, - documentPermissions: `${WORKPLACE_SEARCH_DOCS}workplace-search-sources-document-permissions.html`, - dropbox: `${WORKPLACE_SEARCH_DOCS}workplace-search-dropbox-connector.html`, - externalSharePointOnline: `${WORKPLACE_SEARCH_DOCS}sharepoint-online-external.html`, - externalIdentities: `${WORKPLACE_SEARCH_DOCS}workplace-search-external-identities-api.html`, - gatedFormBlog: `${ELASTIC_WEBSITE_URL}blog/evolution-workplace-search-private-data-elasticsearch`, - gettingStarted: `${WORKPLACE_SEARCH_DOCS}workplace-search-getting-started.html`, - gitHub: `${WORKPLACE_SEARCH_DOCS}workplace-search-github-connector.html`, - gmail: `${WORKPLACE_SEARCH_DOCS}workplace-search-gmail-connector.html`, - googleDrive: `${WORKPLACE_SEARCH_DOCS}workplace-search-google-drive-connector.html`, - indexingSchedule: `${WORKPLACE_SEARCH_DOCS}workplace-search-customizing-indexing-rules.html#_indexing_schedule`, - jiraCloud: `${WORKPLACE_SEARCH_DOCS}workplace-search-jira-cloud-connector.html`, - jiraServer: `${WORKPLACE_SEARCH_DOCS}workplace-search-jira-server-connector.html`, - networkDrive: `${WORKPLACE_SEARCH_DOCS}network-drives.html`, - oneDrive: `${WORKPLACE_SEARCH_DOCS}workplace-search-onedrive-connector.html`, - 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`, - 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`, - sharePointServer: `${WORKPLACE_SEARCH_DOCS}sharepoint-server.html`, - slack: `${WORKPLACE_SEARCH_DOCS}workplace-search-slack-connector.html`, - synch: `${WORKPLACE_SEARCH_DOCS}workplace-search-customizing-indexing-rules.html`, - zendesk: `${WORKPLACE_SEARCH_DOCS}workplace-search-zendesk-connector.html`, - }, metricbeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}`, configure: `${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}/configuring-howto-metricbeat.html`, diff --git a/src/platform/packages/shared/kbn-doc-links/src/types.ts b/src/platform/packages/shared/kbn-doc-links/src/types.ts index fd1c2cf8fe3ca..da1de5e03c3df 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/types.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/types.ts @@ -87,33 +87,6 @@ export interface DocLinks { readonly install: string; readonly start: string; }; - readonly appSearch: { - readonly adaptiveRelevance: string; - readonly apiRef: string; - readonly apiClients: string; - readonly apiKeys: string; - readonly authentication: string; - readonly crawlRules: string; - readonly curations: string; - readonly duplicateDocuments: string; - readonly elasticsearchIndexedEngines: string; - readonly entryPoints: string; - readonly gettingStarted: string; - readonly guide: string; - readonly indexingDocuments: string; - readonly indexingDocumentsSchema: string; - readonly logSettings: string; - readonly metaEngines: string; - readonly precisionTuning: string; - readonly relevanceTuning: string; - readonly resultSettings: string; - readonly searchUI: string; - readonly security: string; - readonly synonyms: string; - readonly webCrawler: string; - readonly webCrawlerEventLogs: string; - readonly webCrawlerReference: string; - }; readonly enterpriseSearch: { readonly aiSearchDoc: string; readonly aiSearchHelp: string; @@ -195,41 +168,6 @@ export interface DocLinks { readonly troubleshootSetup: string; readonly usersAccess: string; }; - readonly workplaceSearch: { - readonly apiKeys: string; - readonly box: string; - readonly confluenceCloud: string; - readonly confluenceCloudConnectorPackage: string; - readonly confluenceServer: string; - readonly contentSources: string; - readonly customConnectorPackage: string; - readonly customSources: string; - readonly customSourcePermissions: string; - readonly documentPermissions: string; - readonly dropbox: string; - readonly externalSharePointOnline: string; - readonly externalIdentities: string; - readonly gatedFormBlog: string; - readonly gitHub: string; - readonly gettingStarted: string; - readonly gmail: string; - readonly googleDrive: string; - readonly indexingSchedule: string; - readonly jiraCloud: string; - readonly jiraServer: string; - readonly networkDrive: string; - readonly oneDrive: string; - readonly permissions: string; - readonly privateSourcePermissions: string; - readonly salesforce: string; - readonly security: string; - readonly serviceNow: string; - readonly sharePoint: string; - readonly sharePointServer: string; - readonly slack: string; - readonly synch: string; - readonly zendesk: string; - }; readonly heartbeat: { readonly base: string; }; diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index f5e1ae42e71ab..6310d22469bf3 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -118,7 +118,7 @@ The value of the parameter `indexID` needs to be passed to the API service when ## Guides config -To use the API service, you need to know a guide ID (currently one of `appSearch`, `websiteSearch`, `databaseSearch`, `kubernetes`, `siem`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). The consumers of guided onboarding register their guide configs themselves and have therefore full control over the guide ID and step IDs used for their guide. For more details on registering a guide config, see below. +To use the API service, you need to know a guide ID (currently one of `websiteSearch`, `databaseSearch`, `kubernetes`, `siem`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). The consumers of guided onboarding register their guide configs themselves and have therefore full control over the guide ID and step IDs used for their guide. For more details on registering a guide config, see below. ## Server side: register a guide config diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index dd327dd4706d5..3f79619d29137 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -145,8 +145,6 @@ export const applicationUsageSchema = { enterpriseSearchVectorSearch: commonSchema, enterpriseSearchElasticsearch: commonSchema, entity_manager: commonSchema, - appSearch: commonSchema, - workplaceSearch: commonSchema, searchExperiences: commonSchema, graph: commonSchema, logs: commonSchema, diff --git a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts index 009e3918b9fab..f5119b5cd15ae 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/schema.ts @@ -36,8 +36,6 @@ const uiMetricFromDataPluginSchema: MakeSchemaFrom = { csm: commonSchema, canvas: commonSchema, enterpriseSearch: commonSchema, - appSearch: commonSchema, - workplaceSearch: commonSchema, graph: commonSchema, logs: commonSchema, metrics: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 8ba1367641679..6e4164389b8a5 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -3277,268 +3277,6 @@ } } }, - "appSearch": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "workplaceSearch": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, "searchExperiences": { "properties": { "appId": { @@ -11968,44 +11706,6 @@ } } }, - "appSearch": { - "type": "array", - "items": { - "properties": { - "key": { - "type": "keyword", - "_meta": { - "description": "The event that is tracked" - } - }, - "value": { - "type": "long", - "_meta": { - "description": "The value of the event" - } - } - } - } - }, - "workplaceSearch": { - "type": "array", - "items": { - "properties": { - "key": { - "type": "keyword", - "_meta": { - "description": "The event that is tracked" - } - }, - "value": { - "type": "long", - "_meta": { - "description": "The value of the event" - } - } - } - } - }, "graph": { "type": "array", "items": { diff --git a/test/api_integration/apis/guided_onboarding/get_config.ts b/test/api_integration/apis/guided_onboarding/get_config.ts index 6ab2095e594b8..617401e34d70d 100644 --- a/test/api_integration/apis/guided_onboarding/get_config.ts +++ b/test/api_integration/apis/guided_onboarding/get_config.ts @@ -18,7 +18,7 @@ export default function testGetGuideConfig({ getService }: FtrProviderContext) { describe(`GET ${getConfigsPath}`, () => { // check that production guides are present - ['siem', 'appSearch', 'websiteSearch', 'databaseSearch', 'kubernetes'].map((guideId) => { + ['siem', 'websiteSearch', 'databaseSearch', 'kubernetes'].map((guideId) => { it(`returns config for ${guideId}`, async () => { const response = await supertest .get(`${getConfigsPath}/${guideId}`) diff --git a/test/api_integration/apis/guided_onboarding/get_guides.ts b/test/api_integration/apis/guided_onboarding/get_guides.ts index 029a4b2eb33b7..fef546d06844c 100644 --- a/test/api_integration/apis/guided_onboarding/get_guides.ts +++ b/test/api_integration/apis/guided_onboarding/get_guides.ts @@ -13,7 +13,6 @@ import { guideStateSavedObjectsType, pluginStateSavedObjectsType, } from '@kbn/guided-onboarding-plugin/server/saved_objects/guided_setup'; -import { appSearchGuideId } from '@kbn/enterprise-search-plugin/common/guided_onboarding/search_guide_config'; import { API_BASE_PATH } from '@kbn/guided-onboarding-plugin/common'; import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; @@ -42,19 +41,13 @@ export default function testGetGuidesState({ getService }: FtrProviderContext) { }); it('returns all created guides (active and inactive)', async () => { - await createGuides(kibanaServer, [ - testGuideStep1ActiveState, - { ...testGuideStep1ActiveState, guideId: appSearchGuideId }, - ]); + await createGuides(kibanaServer, [testGuideStep1ActiveState]); const response = await supertest .get(getGuidesPath) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .expect(200); expect(response.body).not.to.be.empty(); - expect(response.body.state).to.eql([ - testGuideStep1ActiveState, - { ...testGuideStep1ActiveState, guideId: appSearchGuideId }, - ]); + expect(response.body.state).to.eql([testGuideStep1ActiveState]); }); }); } diff --git a/test/api_integration/apis/guided_onboarding/put_state.ts b/test/api_integration/apis/guided_onboarding/put_state.ts index c6682be76d528..b32229ed8670a 100644 --- a/test/api_integration/apis/guided_onboarding/put_state.ts +++ b/test/api_integration/apis/guided_onboarding/put_state.ts @@ -21,7 +21,6 @@ import { guideStateSavedObjectsType, } from '@kbn/guided-onboarding-plugin/server/saved_objects/guided_setup'; import { testGuideId } from '@kbn/guided-onboarding'; -import { appSearchGuideId } from '@kbn/enterprise-search-plugin/common/guided_onboarding/search_guide_config'; import { API_BASE_PATH } from '@kbn/guided-onboarding-plugin/common'; import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; @@ -135,10 +134,7 @@ export default function testPutState({ getService }: FtrProviderContext) { it('updates any existing active guides to inactive', async () => { // create an active guide and an inactive guide - await createGuides(kibanaServer, [ - testGuideStep1ActiveState, - { ...testGuideNotActiveState, guideId: appSearchGuideId }, - ]); + await createGuides(kibanaServer, [testGuideStep1ActiveState]); // Create a new guide with isActive: true await supertest @@ -160,17 +156,18 @@ export default function testPutState({ getService }: FtrProviderContext) { }); expect(testGuideSO.attributes.isActive).to.eql(false); - const searchGuideSO = await kibanaServer.savedObjects.get({ - type: guideStateSavedObjectsType, - id: appSearchGuideId, - }); - expect(searchGuideSO.attributes.isActive).to.eql(false); - - const kubernetesGuide = await kibanaServer.savedObjects.get({ - type: guideStateSavedObjectsType, - id: 'kubernetes', - }); - expect(kubernetesGuide.attributes.isActive).to.eql(true); + // TODO: Figure out what to do with this now that appSearchGuideId is gone + // const searchGuideSO = await kibanaServer.savedObjects.get({ + // type: guideStateSavedObjectsType, + // id: appSearchGuideId, + // }); + // expect(searchGuideSO.attributes.isActive).to.eql(false); + + // const kubernetesGuide = await kibanaServer.savedObjects.get({ + // type: guideStateSavedObjectsType, + // id: 'kubernetes', + // }); + // expect(kubernetesGuide.attributes.isActive).to.eql(true); }); it('saves dynamic params if provided', async () => { diff --git a/test/tsconfig.json b/test/tsconfig.json index 1ba594b8ecbdb..fbd75d4c46b5b 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -60,7 +60,6 @@ "@kbn/dev-utils", "@kbn/utility-types", "@kbn/dev-proc-runner", - "@kbn/enterprise-search-plugin", "@kbn/core-saved-objects-server", "@kbn/core-http-common", "@kbn/event-annotation-plugin", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 82df2e0b71b51..515cad53ec6a7 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -3828,7 +3828,6 @@ "guidedOnboarding.quitGuideModal.quitButtonLabel": "Quitter le guide", "guidedOnboardingPackage.gettingStarted.cards.aiSearch.title": "Développer une expérience de recherche optimisée par l'IA", "guidedOnboardingPackage.gettingStarted.cards.apmObservability.title": "Surveiller les performances {lineBreak} de mon application (APM / traçage)", - "guidedOnboardingPackage.gettingStarted.cards.appSearch.title": "Développer une application {lineBreak} au-dessus d'Elasticsearch", "guidedOnboardingPackage.gettingStarted.cards.cloudSecurity.title": "Sécuriser mes ressources cloud {lineBreak} grâce à la gestion du niveau de sécurité du cloud (CSPM)", "guidedOnboardingPackage.gettingStarted.cards.completeLabel": "Guide terminé", "guidedOnboardingPackage.gettingStarted.cards.databaseSearch.title": "Rechercher dans les bases de données {lineBreak} et les systèmes d'entreprise", @@ -16119,924 +16118,11 @@ "xpack.enterpriseSearch.apiKeyConfig.newApiKeyCreatedCalloutLabel": "Nouvelle clé API créée avec succès", "xpack.enterpriseSearch.applications.navTitle": "Développer", "xpack.enterpriseSearch.applications.productName": "Applications", - "xpack.enterpriseSearch.appSearch.actions.restoreDefaultsButonLabel": "Restaurer les valeurs par défaut", - "xpack.enterpriseSearch.appSearch.adminRoleTypeDescription": "Les administrateurs peuvent tout faire, sauf gérer les paramètres de comptes.", - "xpack.enterpriseSearch.appSearch.allEnginesDescription": "L'attribution à tous les moteurs comprend tous les moteurs actuels et futurs tels qu'ils ont été créés ou seront créés et administrés à une date ultérieure.", - "xpack.enterpriseSearch.appSearch.allEnginesLabel": "Attribuer à tous les moteurs", - "xpack.enterpriseSearch.appSearch.analystRoleTypeDescription": "Les analystes peuvent uniquement afficher des documents, Query Tester et les analyses.", - "xpack.enterpriseSearch.appSearch.audit.title": "Audit", - "xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.confirmationPopupMessage": "Voulez-vous vraiment supprimer le domaine \"{domainUrl}\" et tous ses paramètres ?", - "xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.successMessage": "Le domaine \"{domainUrl}\" a été supprimé", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.description": "Vous pouvez ajouter plusieurs domaines au robot d'indexation de ce moteur. Ajoutez un autre domaine ici et modifiez les points d'entrée et les règles d'indexation à partir de la page \"Gérer\".", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.openButtonLabel": "Ajouter un domaine", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.title": "Ajouter un nouveau domaine", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationFailureMessage": "Impossible de vérifier le contenu, car le contrôle des \"Restrictions d'indexation\" a échoué.", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationLabel": "Vérification de contenu", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.entryPointLabel": "Le point d'entrée du robot d'indexation a été défini sur {entryPointValue}", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.errorsTitle": "Un problème est survenu. Veuillez corriger les erreurs et réessayer.", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationDescription": "Le robot d'indexation ne pourra pas indexer le contenu de ce domaine tant que les erreurs ci-dessus n'auront pas été corrigées.", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationTitle": "Ignorer les échecs de validation et continuer", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsFailureMessage": "Impossible de déterminer les restrictions d'indexation, car le contrôle de la \"Connectivité réseau\" a échoué.", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsLabel": "Restrictions d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.initialVaidationLabel": "Validation initiale", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityFailureMessage": "Impossible d'établir une connexion réseau, car le contrôle de la \"Validation initiale\" a échoué.", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityLabel": "Connectivité réseau", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.submitButtonLabel": "Ajouter un domaine", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.testUrlButtonLabel": "Tester l'URL dans le navigateur", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.unexpectedValidationErrorMessage": "Erreur inattendue", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlHelpText": "Les URL de domaine requièrent un protocole et ne peuvent pas contenir de chemins.", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlLabel": "URL de domaine", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.validateButtonLabel": "Valider le domaine", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel": "Indexer automatiquement", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix": "Chaque", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.formDescription": "Ne vous inquiétez pas, nous lancerons une indexation à votre place. {readMoreMessage}.", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.readMoreLink": "En lire plus.", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleDescription": "La planification de l'indexation s'applique à tous les domaines de ce moteur.", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleFrequencyLabel": "Planifier la fréquence", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleUnitsLabel": "Planifier des unités de temps", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage": "L'indexation automatique a été désactivée.", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage": "Votre planification d'indexation automatique a été mise à jour.", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{crawlType} indexation sur {domainCount, plural, one {# domaine} other {# domaines}}", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlDepthLabel": "Profondeur maximale de l'indexation", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlTypeLabel": "Type d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.configurationDocumentationLinkDescription": "En savoir plus sur la configuration des logs des robots d'indexation dans Kibana", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.customEntryPointUrlsTextboxLabel": "URL de points d'entrée personnalisés", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.customSitemapUrlsTextboxLabel": "URL des plans de site personnalisés", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.domainsAccordionButtonLabel": "Ajouter des domaines à votre indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.emptyDomainsMessage": "Veuillez sélectionner un domaine.", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.entryPointsTabLabel": "Points d'entrée", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeaderDescription": "Configurer une indexation unique avec des paramètres personnalisés.", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeadTitle": "Configuration personnalisée de l'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "Inclure les plans de site découverts dans {robotsDotTxt}", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldDescription": "Définir une profondeur d'exploration maximale pour indiquer le nombre de pages que le robot d'exploration doit parcourir. Définir la valeur sur un (1) pour limiter l'indexation aux seuls points d'entrée.", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldLabel": "Profondeur maximale de l'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.seedUrlsAccordionButtonLabel": "URL de base", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.selectedDescriptor": "sélectionné", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.sitemapsTabLabel": "Plans de site", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.startCrawlButtonLabel": "Appliquer et indexer maintenant", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.previewTabLabel": "Aperçu", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.rawJSONTabLabel": "Raw JSON", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.title": "Détails de la requête d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.domainsTitle": "Domaines", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.seedUrlsTitle": "URL de base", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.sitemapUrlsTitle": "URL des plans de site", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.avgResponseTimeLabel": "Réponse moy.", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.clientErrorsLabel": "Erreurs 4xx", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.durationTooltipTitle": "Durée", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.logsDisabledMessage": "Activer les journaux du robot d'indexation dans les paramètres pour obtenir des statistiques d'indexation plus détaillées.", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesTooltip": "URL visitées et extraites pendant l'indexation.", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesTooltipTitle": "Pages visitées", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesVisitedTooltipTitle": "Pages", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.serverErrorsLabel": "Erreurs 5xx", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.urlsTooltip": "URL trouvées par le robot pendant l'indexation, y compris celles qui ne sont pas suivies en raison de la configuration de l'indexation.", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.urlsTooltipTitle": "URL vues", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusBanner.changesCalloutTitle": "Les modifications que vous effectuez maintenant ne prendront effet qu'au début de votre prochaine indexation.", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.cancelCrawlMenuItemLabel": "Annuler l'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.crawlingButtonLabel": "Indexation en cours…", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.pendingButtonLabel": "En attente…", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.retryCrawlButtonLabel": "Réessayer l'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.showSelectedFieldsButtonLabel": "Afficher uniquement les champs sélectionnés", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startACrawlButtonLabel": "Démarrer une indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startingButtonLabel": "Démarrage en cours…", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.stoppingButtonLabel": "Arrêt en cours…", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceled": "Annulé", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceling": "Annulation", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.failed": "Échoué", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.pending": "En attente", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.running": "En cours d'exécution", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.skipped": "Ignoré", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.starting": "Démarrage", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.success": "Succès", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspended": "Suspendu", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspending": "Suspension", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsDescription": "Les recherches d'indexations récentes sont consignées ici. Vous pouvez utiliser l'ID de requête de chaque indexation pour suivre la progression et examiner les événements d'indexation dans les interfaces utilisateur Logs ou Discover de Kibana.", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.crawlType": "Type d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.created": "Créé", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domains": "Domaines", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domainURL": "ID de requête", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.status": "Statut", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.body": "Vous n'avez encore démarré aucune indexation.", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.title": "Aucune demande d'indexation récente", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTitle": "Demandes d'indexation récentes", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.beginsWithLabel": "Commence par", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.containsLabel": "Contient", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.endsWithLabel": "Se termine par", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.regexLabel": "Regex", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.allowLabel": "Autoriser", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.disallowLabel": "Interdire", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.addButtonLabel": "Ajouter une règle d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.deleteSuccessToastMessage": "La règle d'indexation a été supprimée.", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.description": "Créez une règle d'indexation pour inclure ou exclure les pages dont l'URL correspond à la règle. Les règles sont exécutées dans l'ordre séquentiel, et chaque URL est évaluée en fonction de la première correspondance. {link}", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.descriptionLinkText": "En savoir plus sur les règles d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTableHead": "Modèle de chemin", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTooltip": "Le modèle de chemin est une chaîne littérale, à l'exception du caractère astérisque (*), qui est un métacaractère pouvant correspondre à n'importe quel élément.", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.policyTableHead": "Politique", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.regexPathPatternTooltip": "Le modèle de chemin est une expression régulière compatible avec le moteur d'expression régulière du langage Ruby.", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.ruleTableHead": "Règle", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.title": "Règles d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.modalHeaderTitle": "Indexation de domaines sélectionnés", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.selectedDescriptor": "sélectionné", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.startCrawlButtonLabel": "Appliquer et indexer maintenant", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.full": "Pleine", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.partial": "Partielle", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.reAppliedCrawlRules": "Règles d'indexation réappliquées", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.allFieldsLabel": "Tous les champs", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.description": "Le robot d'indexation n'indexe que les pages uniques. Choisissez les champs que le robot d'indexation doit utiliser lorsqu'il recherche les pages en double. Désélectionnez tous les champs de schéma pour autoriser les documents en double dans ce domaine. {documentationLink}.", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.learnMoreMessage": "En savoir plus sur le hachage de contenu", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.resetToDefaultsButtonLabel": "Réinitialiser aux valeurs par défaut", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.selectedFieldsLabel": "Champs sélectionnés", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.showAllFieldsButtonLabel": "Afficher tous les champs", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.title": "Traitement des documents en double", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.cannotUndoMessage": "Cette action ne peut pas être annulée", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.deleteDomainButtonLabel": "Supprimer le domaine", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.description": "Retirez ce domaine de votre robot d'indexation. Cela supprimera également tous les points d'entrée et toutes les règles d'indexation que vous avez configurés. {cannotUndoMessage}.", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.title": "Supprimer le domaine", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.add.successMessage": "Le domaine \"{domainUrl}\" a bien été ajouté", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.buttonLabel": "Supprimer ce domaine", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.manage.buttonLabel": "Gérer ce domaine", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.actions": "Actions", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.documents": "Documents", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.domainURL": "URL de domaine", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.lastActivity": "Dernière activité", - "xpack.enterpriseSearch.appSearch.crawler.domainsTitle": "Domaines", - "xpack.enterpriseSearch.appSearch.crawler.empty.crawlerDocumentationLinkDescription": "En savoir plus sur le robot d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.empty.description": "Indexez facilement le contenu de votre site web. Pour commencer, entrez le nom de votre domaine, fournissez des points d'entrée et des règles d'indexation facultatifs, et nous nous chargeons du reste.", - "xpack.enterpriseSearch.appSearch.crawler.empty.title": "Ajouter un domaine pour commencer", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.addButtonLabel": "Ajouter un point d'entrée", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.description": "Inclure ici les URL les plus importantes pour votre site web. Les URL de point d'entrée seront les premières pages à être indexées et traitées pour servir de liens vers les autres pages.", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageDescription": "{link} pour spécifier un point d'entrée pour le robot d'indexation", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageLinkText": "Ajouter un point d'entrée", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageTitle": "Il n'existe aucun point d'entrée.", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.lastItemMessage": "Le robot d'indexation nécessite au moins un point d'entrée.", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText": "Découvrez plus d'informations sur les points d'entrée.", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.title": "Points d'entrée", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.urlTableHead": "URL", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingButtonLabel": "Indexation automatique", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingTitle": "Indexation automatique", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.manageCrawlsButtonLabel": "Gérer les indexations", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRules.successMessage": "Les règles d'indexation sont en train d'être réappliquées dans l'arrière-plan", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRulesButtonLabel": "Réappliquer les règles d'indexation", "xpack.enterpriseSearch.appSearch.crawler.simplifiedSelectable.deselectAllButtonLabel": "Tout désélectionner", "xpack.enterpriseSearch.appSearch.crawler.simplifiedSelectable.selectAllButtonLabel": "Tout sélectionner", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.addButtonLabel": "Ajouter un plan du site", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.deleteSuccessToastMessage": "Le plan du site a été supprimé.", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.description": "Spécifiez les URL du plan du site pour le robot d'indexation dans ce domaine.", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.emptyMessageTitle": "Il n'existe aucun plan de site.", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.title": "Plans de site", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.urlTableHead": "URL", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlAllDomainsMenuLabel": "Indexation de tous les domaines sur ce moteur", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlCustomSettingsMenuLabel": "Indexation avec des paramètres personnalisés", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlSelectDomainsMenuLabel": "Indexation de domaines sélectionnés", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.startACrawlButtonLabel": "Démarrer une indexation", - "xpack.enterpriseSearch.appSearch.credentials.apiEndpoint": "Point de terminaison", - "xpack.enterpriseSearch.appSearch.credentials.apiKeys": "Clés d'API", - "xpack.enterpriseSearch.appSearch.credentials.copied": "Copié", - "xpack.enterpriseSearch.appSearch.credentials.copyApiEndpoint": "Copiez le point de terminaison d'API dans le presse-papiers.", - "xpack.enterpriseSearch.appSearch.credentials.copyApiKey": "Copier la clé d'API dans le presse-papiers", - "xpack.enterpriseSearch.appSearch.credentials.createKey": "Créer une clé", - "xpack.enterpriseSearch.appSearch.credentials.deleteKey": "Supprimer la clé d'API", - "xpack.enterpriseSearch.appSearch.credentials.documentationLink1": "Consulter la documentation", - "xpack.enterpriseSearch.appSearch.credentials.documentationLink2": "pour en savoir plus sur les clés.", - "xpack.enterpriseSearch.appSearch.credentials.editKey": "Modifier une clé d'API", - "xpack.enterpriseSearch.appSearch.credentials.empty.body": "Autorisez les applications à accéder à Elastic App Search en votre nom.", - "xpack.enterpriseSearch.appSearch.credentials.empty.buttonLabel": "En savoir plus sur les clés d'API", - "xpack.enterpriseSearch.appSearch.credentials.empty.title": "Créer votre première clé d'API", - "xpack.enterpriseSearch.appSearch.credentials.flyout.createTitle": "Créer une nouvelle clé", - "xpack.enterpriseSearch.appSearch.credentials.flyout.updateTitle": "Mettre à jour {tokenName}", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.helpText": "Moteurs auxquels la clé peut accéder :", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.label": "Sélectionner les moteurs", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.helpText": "Accédez à tous les moteurs actuels et futurs.", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.label": "Accès total au moteur", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.label": "Contrôle d'accès au moteur", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.helpText": "Limitez l'accès de la clé à des moteurs spécifiques.", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.label": "Accès limité au moteur", - "xpack.enterpriseSearch.appSearch.credentials.formName.helpText": "Votre clé sera nommée : {name}", - "xpack.enterpriseSearch.appSearch.credentials.formName.label": "Nom de clé", - "xpack.enterpriseSearch.appSearch.credentials.formName.placeholder": "par exemple, ma-clé-de-moteur", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.helpText": "Applicable uniquement aux clés d'API privées.", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.label": "Niveaux d'accès en lecture et en écriture", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.readLabel": "Accès en lecture", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.writeLabel": "Accès en écriture", - "xpack.enterpriseSearch.appSearch.credentials.formType.label": "Type de clé", - "xpack.enterpriseSearch.appSearch.credentials.hideApiKey": "Masquer la clé d'API", - "xpack.enterpriseSearch.appSearch.credentials.list.enginesTitle": "Moteurs", - "xpack.enterpriseSearch.appSearch.credentials.list.keyTitle": "Clé", - "xpack.enterpriseSearch.appSearch.credentials.list.modesTitle": "Modes", - "xpack.enterpriseSearch.appSearch.credentials.list.nameTitle": "Nom", - "xpack.enterpriseSearch.appSearch.credentials.list.typeTitle": "Type", - "xpack.enterpriseSearch.appSearch.credentials.showApiKey": "Afficher la clé d'API", - "xpack.enterpriseSearch.appSearch.credentials.title": "Informations d'identification", - "xpack.enterpriseSearch.appSearch.credentials.updateWarning": "Les clés d'API existantes peuvent être partagées entre les utilisateurs. La modification des autorisations relatives à cette clé affectera tous les utilisateurs disposant d'un accès à cette clé.", - "xpack.enterpriseSearch.appSearch.credentials.updateWarningTitle": "Continuez avec prudence !", - "xpack.enterpriseSearch.appSearch.cta": "Explorer", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonDescription": "Activer les suggestions pour cette requête", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonLabel": "Autoriser", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.allowQuerySuccessMessage": "Vous serez informé des futures suggestions pour cette requête", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.description": "Vous ne serez pas informé des suggestions pour ces requêtes", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.queryColumnName": "Recherche", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.refresh": "Actualiser", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.title": "Requêtes ignorées", - "xpack.enterpriseSearch.appSearch.curations.settings.acceptNewSuggesstionsSwitchLabel": "Accepter automatiquement les nouvelles suggestions", - "xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutDescription": "La pertinence adaptative exige que l'analytique soit activée sur votre compte.", - "xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutTitle": "L'analytique est désactivée", - "xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsDescription": "App Search effectuera le monitoring de l'analytique de votre moteur et suggérera des modifications à vos curations pour vous aider à fournir les résultats les plus pertinents. Chaque suggestion peut être acceptée, rejetée ou modifiée.", - "xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsTitle": "Des curations alimentées par la pertinence adaptative", - "xpack.enterpriseSearch.appSearch.curations.settings.enableautomaticCurationsSwitchLabel": "Activer les suggestions", - "xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTASubtitle": "Mettez à niveau vers un abonnement {platinumLicenseName} pour exploiter la puissance du Machine Learning. En analysant l'analytique de votre moteur, App Search est en mesure de suggérer des curations nouvelles ou mises à jour. Aidez facilement vos utilisateurs à trouver exactement ce qu'ils recherchent. Démarrez une version d'essai gratuite.", - "xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTATitle": "Introduction de curations alimentées par la pertinence adaptative", - "xpack.enterpriseSearch.appSearch.curations.settings.manageAnalyticsButtonLabel": "Gérer l'analytique", - "xpack.enterpriseSearch.appSearch.curations.settings.platinum": "Platinum", - "xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION": "Les développeurs peuvent gérer tous les aspects d'un moteur.", - "xpack.enterpriseSearch.appSearch.documentCreation.api.example": "Pour voir l'API en action, vous pouvez tester l'exemple de requête ci-dessous à l'aide d'une ligne de commande ou d'une bibliothèque client.", - "xpack.enterpriseSearch.appSearch.documentCreation.api.title": "Indexation par API", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.apiDescription": "POST vers le point de terminaison des documents", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.apiTitle": "Index de l'API", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.crawlDescription": "Indexation automatique des documents à partir d'une URL", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.crawlTitle": "Utiliser le robot d'indexation", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterLink": "Lire la documentation", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterText": "Vous voulez en savoir plus sur l'indexation des documents ?", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateIllustrationAltText": "Illustration", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateTitle": "Ajouter des documents", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.jsonDescription": "Ajoutez des documents en collant ou en téléchargeant Raw JSON", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.jsonTitle": "Coller ou charger JSON", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.button": "Connecter pour indexer", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.description": "Vous pouvez désormais vous connecter directement à un index Elasticsearch existant pour rendre ses données interrogeables et ajustables par le biais d'interfaces utilisateur Enterprise Search. {learnMoreLink}", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.link": "En savoir plus sur l'utilisation d'un index existant", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.title": "Connecter un index Elasticsearch", - "xpack.enterpriseSearch.appSearch.documentCreation.errorsTitle": "Un problème est survenu. Veuillez corriger les erreurs et réessayer.", - "xpack.enterpriseSearch.appSearch.documentCreation.helperText": "Il existe trois façons d'envoyer des documents à votre moteur pour l'indexation. Vous pouvez coller ou télécharger un fichier JSON, envoyer un message POST au point de terminaison de l'API des documents, ou utiliser le robot d'indexation d'Elastic pour indexer automatiquement les documents à partir d'une URL.", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.pasteTabName": "Coller", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.title": "Coller ou charger JSON", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.uploadTabName": "Charger", - "xpack.enterpriseSearch.appSearch.documentCreation.largeFile": "Vous êtes en train de charger un fichier extrêmement volumineux. Cette opération risque de bloquer votre navigateur ou de prendre beaucoup de temps à traiter. Si possible, essayez de diviser vos données en plusieurs fichiers plus petits.", - "xpack.enterpriseSearch.appSearch.documentCreation.noFileFound": "Aucun fichier trouvé.", - "xpack.enterpriseSearch.appSearch.documentCreation.notValidJson": "Le contenu du document doit être un objet ou un tableau JSON valide.", - "xpack.enterpriseSearch.appSearch.documentCreation.noValidFile": "Problème lors de l'analyse du fichier.", - "xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.description": "Collez un tableau de documents JSON. Vérifiez que le JSON est valide et que la taille de chaque objet de document est inférieure à {maxDocumentByteSize} octets.", - "xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.label": "Coller le JSON ici", - "xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title": "Ajouter de nouveaux documents", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.documentNotIndexed": "Ce document n'a pas été indexé !", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.fixErrors": "Corriger les erreurs", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.invalidDocuments": "{invalidDocuments, number} {invalidDocuments, plural, one {document} other {documents}} avec erreurs...", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.newDocuments": "{newDocuments, number} {newDocuments, plural, one {document ajouté} other {documents ajoutés}}.", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.newSchemaFields": "{newFields, number} {newFields, plural, one {champ ajouté} other {champs ajoutés}} au schéma du moteur.", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.noNewDocuments": "Aucun nouveau document.", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.noNewSchemaFields": "Aucun nouveau champ de schéma.", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.otherDocuments": "et {documents, number} {documents, plural, one {autre document} other {autres documents}}.", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.title": "Résumé de l'indexation", - "xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.label": "Si vous avez un fichier .json, chargez-le ou effectuez un glisser-déposer du fichier. Vérifiez que le JSON est valide et que la taille de chaque objet de document est inférieure à {maxDocumentByteSize} octets.", - "xpack.enterpriseSearch.appSearch.documentCreation.warningsTitle": "Avertissement !", - "xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete": "Voulez-vous vraiment supprimer ce document ?", - "xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess": "Votre document a été supprimé", - "xpack.enterpriseSearch.appSearch.documentDetail.fieldHeader": "Champ", - "xpack.enterpriseSearch.appSearch.documentDetail.title": "Document : {documentId}", - "xpack.enterpriseSearch.appSearch.documentDetail.valueHeader": "Valeur", - "xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout": "Le moteur est attaché à {elasticsearchIndexName}. Vous pouvez modifier les données de cet index dans Kibana.", - "xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout.title": "Les données de ce moteur sont gérées par Elasticsearch.", - "xpack.enterpriseSearch.appSearch.documents.empty.description": "Vous pouvez indexer des documents à l'aide du robot d'indexation App Search en chargeant le JSON ou en utilisant l'API.", - "xpack.enterpriseSearch.appSearch.documents.empty.title": "Ajouter vos premiers documents", - "xpack.enterpriseSearch.appSearch.documents.indexDocuments": "Indexer les documents", - "xpack.enterpriseSearch.appSearch.documents.metaEngineCallout": "Les métamoteurs ont plusieurs moteurs sources. Accédez à vos moteurs sources pour modifier leurs documents.", - "xpack.enterpriseSearch.appSearch.documents.metaEngineCallout.title": "Vous êtes à l'intérieur d'un métamoteur.", - "xpack.enterpriseSearch.appSearch.documents.paging.ariaLabelBottom": "Pagination des résultats de recherche en bas des résultats", - "xpack.enterpriseSearch.appSearch.documents.paging.ariaLabelTop": "Pagination des résultats de recherche en haut des résultats", - "xpack.enterpriseSearch.appSearch.documents.search.ariaLabel": "Filtrer les documents", - "xpack.enterpriseSearch.appSearch.documents.search.customizationButton": "Personnaliser les filtres et trier", - "xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.button": "Personnaliser", - "xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.message": "Saviez-vous que vous pouvez personnaliser votre expérience de recherche de document ? Cliquez sur \"Personnaliser\" ci-dessous pour commencer.", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFields": "Valeurs à facettes rendues en tant que filtres et disponibles comme affinement de recherche", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFieldsLabel": "Champs de filtre", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.sortFields": "Utilisé pour afficher les options de tri des résultats, par ordre croissant et décroissant", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.sortFieldsLabel": "Champs de tri", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.title": "Personnaliser la recherche de documents", - "xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.noValue.selectOption": "", - "xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.showMore": "Afficher plus", - "xpack.enterpriseSearch.appSearch.documents.search.noResults": "Aucun résultat pour \"{resultSearchTerm}\" pour le moment !", - "xpack.enterpriseSearch.appSearch.documents.search.placeholder": "Filtrer les documents…", - "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "Nombre de résultats à afficher par page", - "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "Afficher :", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy": "Trier par", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.ariaLabel": "Trier les résultats par", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.ascendingDropDownOptionLabel": "{fieldName} (croiss.)", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.descendingDropDownOptionLabel": "{fieldName} (décroiss.)", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.documentId": "ID de document", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.relevance": "Pertinence", - "xpack.enterpriseSearch.appSearch.documents.title": "Documents", - "xpack.enterpriseSearch.appSearch.editorRoleTypeDescription": "Les éditeurs peuvent gérer les paramètres de recherche.", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateButton": "Gestion des index", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateIllustrationAltText": "Illustration", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateTitle": "Ajouter des documents à votre index", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.helperText": "Votre index Elasticsearch, {elasticsearchIndexName}, n'a pas encore de documents. Ouvrez la gestion des index dans Kibana pour apporter des modifications à vos index Elasticsearch.", - "xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta": "Créer un moteur", - "xpack.enterpriseSearch.appSearch.emptyState.description1": "Un moteur App Search stocke les documents pour votre expérience de recherche.", - "xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description": "Contactez votre administrateur App Search pour créer un accès ou vous donner accès à un moteur.", - "xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title": "Aucun moteur n'est disponible", - "xpack.enterpriseSearch.appSearch.emptyState.title": "Créer votre premier moteur", - "xpack.enterpriseSearch.appSearch.engine.analytics.allTagsDropDownOptionLabel": "Toutes les balises d'analyse", - "xpack.enterpriseSearch.appSearch.engine.analytics.clickTablesDescription": "Découvrez quelles recherches ont généré le plus grand nombre et le plus petit nombre de clics.", - "xpack.enterpriseSearch.appSearch.engine.analytics.clickTablesTitle": "Analyse des clics", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.applyButtonLabel": "Appliquer des filtres", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel": "Filtrer par date de fin", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel": "Filtrer par date de début", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.tagAriaLabel": "Filtrer par balise d'analyse\"", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.cardDescription": "Recherches pour {queryTitle}", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.chartTooltip": "Recherches par jour", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.tableDescription": "Documents avec le plus grand nombre de clics résultant de cette recherche.", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.tableTitle": "Nombre de clics le plus élevé", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title": "Recherche", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchButtonLabel": "Afficher les détails", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchPlaceholder": "Accéder au terme de recherche", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryTablesDescription": "Obtenez des informations sur les recherches les plus fréquentes et celles qui n'ont renvoyé aucun résultat.", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryTablesTitle": "Analyse des recherches", - "xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesDescription": "Vue des recherches en cours en ce moment.", - "xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesTitle": "Recherches récentes", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.clicksColumn": "Clics", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.editTooltip": "Gérer la curation", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksDescription": "Aucun document n'a reçu de clic à partir de cette recherche.", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksTitle": "Aucun clic", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesDescription": "Aucune recherche n'a été effectuée pendant cette période.", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesTitle": "Aucune recherche à afficher", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesDescription": "Les recherches apparaîtront ici au fur et à mesure de leur réception.", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesTitle": "Aucune recherche récente", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.moreTagsBadge": "et {moreTagsCount} de plus", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.queriesColumn": "Recherches", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.resultsColumn": "Résultats", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsColumn": "Balises d'analyse", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsCountBadge": "{tagsCount, plural, one {# balise} other {# balises}}", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.termColumn": "Terme de recherche", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.timeColumn": "Heure", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAction": "Afficher", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAllButtonLabel": "Afficher tout", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewTooltip": "Afficher l'analyse des recherches", - "xpack.enterpriseSearch.appSearch.engine.analytics.title": "Analyse", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoClicksTitle": "Recherches sans clic les plus fréquentes", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoResultsTitle": "Recherches sans résultat les plus fréquentes", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesTitle": "Recherches les plus fréquentes", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesWithClicksTitle": "Recherches avec clics les plus fréquentes", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations": "Total des opérations d'API", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks": "Total des clics", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments": "Total des documents", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries": "Total des recherches", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalQueriesNoResults": "Total des recherches sans résultat", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.detailsButtonLabel": "Détails", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel": "Afficher la référence d'API", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyDescription": "Les logs seront mis à jour en temps réel lors de la réception d'une requête API.", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyTitle": "Aucun événement d'API au cours des dernières 24 heures", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.endpointTableHeading": "Point de terminaison", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.flyout.title": "Détails de la requête", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTableHeading": "Méthode", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTitle": "Méthode", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorDescription": "Veuillez vérifier votre connexion ou recharger manuellement la page.", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorMessage": "Impossible d'actualiser les données de log de l'API", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.recent": "Événements d'API récents", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.requestBodyTitle": "Corps de la requête", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.requestPathTitle": "Chemin de la requête", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.responseBodyTitle": "Corps de la réponse", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTableHeading": "Statut", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTitle": "Statut", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.timestampTitle": "Horodatage", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.timeTableHeading": "Heure", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.title": "Logs d'API", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.userAgentTitle": "Agent utilisateur", - "xpack.enterpriseSearch.appSearch.engine.crawler.title": "Robot d'indexation", - "xpack.enterpriseSearch.appSearch.engine.curation.automatedLabel": "Automatisé", - "xpack.enterpriseSearch.appSearch.engine.curation.convertToManualCurationButtonLabel": "Convertir à la curation manuelle", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyButtonLabel": "Historique", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription": "Un log détaillé des modifications récentes des curations alimentées par la pertinence adaptative.", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle": "Changements de pertinence adaptative", - "xpack.enterpriseSearch.appSearch.engine.curation.suggestedDocumentsCallout.description": "Sur la base de l'analytique de votre moteur, de nouvelles promotions de documents suggérées sont prêtes à être révisées.", - "xpack.enterpriseSearch.appSearch.engine.curation.suggestedDocumentsCallout.title": "Nouveaux documents suggérés pour cette requête", - "xpack.enterpriseSearch.appSearch.engine.curations.actionsPopoverAriaLabel": "Autres actions de suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.activeQueryHelpText": "Sélectionnez une requête pour afficher les résultats de recherche organique qui la concernent", - "xpack.enterpriseSearch.appSearch.engine.curations.activeQueryLabel": "Recherche active", - "xpack.enterpriseSearch.appSearch.engine.curations.addQueryButtonLabel": "Ajouter une recherche", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel": "Ajouter le résultat manuellement", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchEmptyDescription": "Impossible de trouver un contenu correspondant.", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder": "Documents du moteur de recherche", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.title": "Ajouter le résultat à la curation", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.queryColumnHeader": "Recherche", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableDecription": "Un log détaillé des modifications récentes des curations alimentées par la pertinence adaptative.", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableTitle": "Changements de pertinence adaptative", - "xpack.enterpriseSearch.appSearch.engine.curations.convertToManualCurationConfirmation": "Êtes-vous sûr de vouloir convertir cela en curation manuelle ?", - "xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesDescription": "Ajoutez une ou plusieurs recherches à préparer. Vous pourrez ajouter ou supprimer des requêtes supplémentaires ultérieurement.", - "xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesTitle": "Recherches de curation", - "xpack.enterpriseSearch.appSearch.engine.curations.create.title": "Créer une curation", - "xpack.enterpriseSearch.appSearch.engine.curations.deleteConfirmation": "Voulez-vous vraiment supprimer cette curation ?", - "xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage": "Votre curation a été supprimée", - "xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel": "Rétrograder ce résultat", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel": "Lire le guide des curations", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.description": "Utilisez les curations pour mettre en avant et masquer des documents. Aidez les gens à découvrir ce que vous aimeriez le plus qu'ils découvrent.", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.noCurationsTitle": "Créez votre première curation", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription": "Masquez les documents en cliquant sur l'icône de l'œil dans les résultats organiques ci-dessus, ou recherchez et masquez un résultat manuellement.", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle": "Vous n'avez encore masqué aucun document", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel": "Tout démasquer", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title": "Résultats cachés", - "xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel": "Masquer ce résultat", - "xpack.enterpriseSearch.appSearch.engine.curations.historyPageTabLabel": "Historique", - "xpack.enterpriseSearch.appSearch.engine.curations.manage.title": "Gérer la curation", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel": "Gérer les requêtes", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryDescription": "Modifiez, ajoutez ou supprimez des requêtes pour cette curation.", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryTitle": "Gérer les requêtes", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.description": "Aucun résultat organique à afficher.{manualDescription}", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.manualDescription": "Ajoutez ou modifiez la requête active ci-dessus.", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.title": "Principaux documents organiques pour \"{currentQuery}\"", - "xpack.enterpriseSearch.appSearch.engine.curations.overview.title": "Résultats pertinents", - "xpack.enterpriseSearch.appSearch.engine.curations.overviewPageTabLabel": "Aperçu", - "xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel": "Mettre en avant ce résultat", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.automatedEmptyDescription": "Nous n'avons pas identifié de documents à promouvoir", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.emptyDescription": "Marquez les documents des résultats organiques ci-dessous par une étoile, ou recherchez et mettez en avant un résultat manuellement.", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.managedByAppSearchDescription": "Cette curation est automatisée par App Search", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel": "Tout rétrograder", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title": "Résultats mis en avant", - "xpack.enterpriseSearch.appSearch.engine.curations.queryPlaceholder": "Entrer une recherche", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.queryColumnHeader": "Recherche", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableDescription": "Afficher les suggestions que vous avez précédemment rejetées.", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableTitle": "Suggestions récemment rejetées", - "xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation": "Voulez-vous vraiment effacer vos modifications et revenir à vos résultats par défaut ?", - "xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription": "Mettez les documents en avant en cliquant sur l'étoile et masquez-les en cliquant sur l'œil.", - "xpack.enterpriseSearch.appSearch.engine.curations.settingsPageTabLabel": "Paramètres", - "xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel": "Afficher ce résultat", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.acceptButtonLabel": "Accepter", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAcceptButtonLabel": "Accepter cette suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAutomateButtonLabel": "Automatiser - toujours accepter de nouvelles suggestions pour cette requête", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsPopoverTitle": "Gérer la suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsRejectButtonLabel": "Rejeter cette suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsTurnOffButtonLabel": "Rejeter et désactiver les suggestions pour cette requête", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.breadcrumbLabel": "Suggérée : {query}", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.collapseButtonLabel": "Réduire les résultats de recherche organiques", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.currentTitle": "Actuel", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.expandButtonLabel": "Développer les résultats de recherche organiques", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsDescription": "Aucun résultat de recherche organique n'a été obtenu pour cette requête", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsTitle": "Aucun résultat", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noResultsMessage": "Il n'y a actuellement aucun document mis en avant pour cette requête", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.notFoundError": "Impossible de trouver la suggestion ; elle a peut-être déjà été appliquée ou rejetée.", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.rejectButtonLabel": "Rejeter", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelDescription": "Cette curation peut être automatisée par App Search", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelTitle": "Résultats mis en avant", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAppliedMessage": "La suggestion a bien été appliquée.", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAutomatedMessage": "La suggestion a bien été appliquée et toutes les suggestions futures pour la requête \"{query}\" seront automatiquement appliquées.", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyDisabledMessage": "La suggestion a bien été rejetée et vous ne recevrez plus de suggestions pour la requête \"{query}\".", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyRejectedMessage": "La suggestion a bien été rejetée.", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.suggestionTitle": "Suggérée", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.title": "Gérer la suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.hideForNowLabel": "Masquer ceci pour l'instant", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.reviewSuggestionsButtonLabel": "Réviser les suggestions", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.lastUpdatedTableHeader": "Dernière mise à jour", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.promotedDocumentsTableHeader": "Résultats mis en avant", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.queryTableHeader": "Recherche", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.description": "D'après votre analytique, les résultats des requêtes suivantes pourraient être améliorés en promouvant certains documents.", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.overridesLabel": "Remplacements", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.title": "{totalSuggestions} Suggestions", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.viewTooltip": "Afficher la suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.table.automatedLabel": "Automatisé", - "xpack.enterpriseSearch.appSearch.engine.curations.table.column.lastUpdated": "Dernière mise à jour", - "xpack.enterpriseSearch.appSearch.engine.curations.table.column.queries": "Recherches", - "xpack.enterpriseSearch.appSearch.engine.curations.table.deleteTooltip": "Supprimer la curation", - "xpack.enterpriseSearch.appSearch.engine.curations.table.editTooltip": "Modifier la curation", - "xpack.enterpriseSearch.appSearch.engine.curations.table.newSuggestionLabel": "Nouvelle suggestion", - "xpack.enterpriseSearch.appSearch.engine.curations.table.title": "Curations actives", - "xpack.enterpriseSearch.appSearch.engine.curations.title": "Curations", - "xpack.enterpriseSearch.appSearch.engine.documents.empty.buttonLabel": "Lire le guide de documents", - "xpack.enterpriseSearch.appSearch.engine.elasticsearchEngineBadge": "INDEX ELASTICSEARCH", - "xpack.enterpriseSearch.appSearch.engine.metaEngineBadge": "MÉTAMOTEUR", - "xpack.enterpriseSearch.appSearch.engine.notFound": "Impossible de trouver un moteur portant le nom \"{engineName}\".", - "xpack.enterpriseSearch.appSearch.engine.overview.analyticsLink": "Afficher l'analyse", - "xpack.enterpriseSearch.appSearch.engine.overview.apiLogsLink": "Afficher les logs d'API", - "xpack.enterpriseSearch.appSearch.engine.overview.chartDuration": "7 derniers jours", - "xpack.enterpriseSearch.appSearch.engine.overview.empty.heading": "Configuration du moteur", - "xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction": "Afficher la documentation", - "xpack.enterpriseSearch.appSearch.engine.overview.heading": "Aperçu du moteur", - "xpack.enterpriseSearch.appSearch.engine.overview.title": "Aperçu", - "xpack.enterpriseSearch.appSearch.engine.pollingErrorDescription": "Veuillez vérifier votre connexion ou recharger manuellement la page.", - "xpack.enterpriseSearch.appSearch.engine.pollingErrorMessage": "Impossible de récupérer les données du moteur", - "xpack.enterpriseSearch.appSearch.engine.queryTester.searchPlaceholder": "Documents du moteur de recherche", - "xpack.enterpriseSearch.appSearch.engine.queryTesterTitle": "Query Tester", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addBoostDropDownOptionLabel": "Ajouter un boost", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addOperationDropDownOptionLabel": "Ajouter", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.deleteBoostButtonLabel": "Supprimer le boost", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.exponentialFunctionDropDownOptionLabel": "Exponentiel", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.functionalDropDownOptionLabel": "Fonctionnel", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.funtional.functionDropDownLabel": "Fonction", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.funtional.operationDropDownLabel": "Opération", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.gaussianFunctionDropDownOptionLabel": "Gaussien", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.impactLabel": "Impact", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.linearFunctionDropDownOptionLabel": "Linéaire", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.logarithmicBoostFunctionDropDownOptionLabel": "Logarithmique", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.multiplyOperationDropDownOptionLabel": "Multiplier", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximity.centerLabel": "Centre", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximity.functionDropDownLabel": "Fonction", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximityDropDownOptionLabel": "Proximité", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.title": "Boosts", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.valueDropDownOptionLabel": "Valeur", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description": "Gérer les paramètres de précision et de pertinence de votre moteur", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFields.title": "Champs désactivés", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFieldsExplanationMessage": "Inactif en raison d'un conflit de type champ", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.elasticsearch.description": "Gérer les paramètres de pertinence de votre moteur", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel": "Lire le guide de réglage de la pertinence", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.description": "Un schéma sera automatiquement créé à votre place après votre indexation de quelques documents.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.title": "Ajouter des documents pour régler la pertinence", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoosts": "Boosts non valides", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsBannerLabel": "Certains boosts ne sont pas valides !", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsErrorMessage": "Un ou plusieurs de vos boosts ne sont plus valides, peut-être en raison d'un changement de type de schéma. Supprimez tout boost ancien ou non valide pour rejeter cette alerte.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder": "Filtrer les champs {schemaFieldsLength}...", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.descriptionLabel": "Rechercher dans ce champ", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.rowLabel": "Recherche de texte", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.warningLabel": "La recherche ne peut être activée que sur des champs de texte", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title": "Gérer les champs", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.weight.label": "Pondération", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteConfirmation": "Voulez-vous vraiment supprimer ce boost ?", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteSuccess": "La pertinence a été réinitialisée aux valeurs par défaut", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.resetConfirmation": "Voulez-vous vraiment restaurer les valeurs par défaut de la pertinence ?", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.successDescription": "Les modifications auront bientôt un impact sur vos résultats.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.updateSuccess": "La pertinence a été réglée", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.ariaLabel": "Rappel et précision", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.description": "Ajustez les paramètres entre précision et rappel sur votre moteur.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link": "En savoir plus.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.precision.label": "Précision", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.recall.label": "Rappel", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step01.description": "Configuration avec rappel le plus élevé et précision la moins élevée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step02.description": "Par défaut : Moins de la moitié des termes doit correspondre. Une tolérance typographique totale est appliquée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step03.description": "Augmentation des exigences relatives aux termes : pour correspondre, les documents doivent contenir tous les termes d'une recherche comprenant jusqu'à deux termes, et la moitié des termes si la recherche en comprend davantage. Une tolérance typographique totale est appliquée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step04.description": "Augmentation des exigences relatives aux termes : pour correspondre, les documents doivent contenir tous les termes d'une recherche comprenant jusqu'à trois termes, et les trois-quarts des termes si la recherche en comprend davantage. Une tolérance typographique totale est appliquée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step05.description": "Augmentation des exigences relatives aux termes : pour correspondre, les documents doivent contenir tous les termes d'une recherche comprenant jusqu'à quatre termes, et tous les termes moins un si la recherche en comprend davantage. Une tolérance typographique totale est appliquée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step06.description": "Augmentation des exigences relatives aux termes : pour correspondre, les documents doivent contenir tous les termes pour toutes les recherches. Une tolérance typographique totale est appliquée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step07.description": "Exigences les plus strictes relatives aux termes : pour correspondre, les documents doivent contenir tous les termes dans le même champ. Une tolérance typographique totale est appliquée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step08.description": "Exigences les plus strictes relatives aux termes : pour correspondre, les documents doivent contenir tous les termes dans le même champ. Une tolérance typographique partielle est appliquée : la correspondance approximative est désactivée.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step09.description": "Exigences les plus strictes relatives aux termes : pour correspondre, les documents doivent contenir tous les termes dans le même champ. Une tolérance typographique partielle est appliquée : la correspondance approximative et l'ajout de préfixes sont désactivés.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step10.description": "Exigences les plus strictes relatives aux termes : pour correspondre, les documents doivent contenir tous les termes dans le même champ. Une tolérance typographique partielle est appliquée : en plus de ce qui précède, les contractions et les coupures de mots ne sont pas corrigées.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step11.description": "Seules les correspondances exactes sont appliquées, avec une tolérance uniquement pour les différences entre lettres majuscules et minuscules.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.title": "Réglage de la précision", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.enterQueryMessage": "Entrer une recherche pour afficher les résultats", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.noResultsMessage": "Impossible de trouver de contenu correspondant", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.searchPlaceholder": "Rechercher {engineName}", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title": "Aperçu", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaConflictsBannerLabel": "Champs désactivés", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaConflictsErrorMessage": "{schemaFieldsWithConflictsCount, number} {schemaFieldsWithConflictsCount, plural, one {champ inactif} other {champs inactifs}} en raison de conflits de type champ. {link}", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaFieldsLinkLabel": "champs de schéma", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title": "Réglage de la pertinence", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.uncofirmedFieldsBannerLabel": "Par défaut, aucune recherche n'est effectuée dans les champs récemment ajoutés", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.uncofirmedFieldsErrorMessage": "Si ces nouveaux champs doivent être interrogeables, activez-les ici à l'aide du bouton bascule Recherche de texte. Sinon, confirmez votre nouveau {schemaLink} pour rejeter cette alerte.", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.unsearchedFields": "Champs non interrogés", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.whatsThisLinkLabel": "Qu'est-ce que c'est ?", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.clearButtonLabel": "Effacer toutes les valeurs", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmResetMessage": "Voulez-vous vraiment restaurer les valeurs par défaut des paramètres de résultat ? Tous les champs reprendront leurs valeurs brutes sans limites.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmSaveMessage": "Les modifications commenceront immédiatement. Assurez-vous que vos applications sont prêtes à accepter ces nouveaux résultats de recherche !", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.buttonLabel": "Lire le guide des paramètres de résultat", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.description": "Un schéma sera automatiquement créé à votre place après votre indexation de quelques documents.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.title": "Ajouter des documents pour régler les paramètres", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.fieldTypeConflictText": "Conflit de type champ", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.numberFieldPlaceholder": "Aucune limite", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.pageDescription": "Enrichissez les résultats de recherche et sélectionnez les champs qui doivent apparaître.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.delayedValue": "retardé", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.goodValue": "bon", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.optimalValue": "optimal", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.standardValue": "standard", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformanceLabel": "Performances des recherches : {performanceValue}", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.errorMessage": "Une erreur s'est produite.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.inputPlaceholder": "Tapez une recherche pour tester une réponse…", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.noResultsMessage": "Aucun résultat.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponseTitle": "Exemple de réponse", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage": "Les paramètres des résultats ont été enregistrés", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.disabledFieldsTitle": "Champs désactivés", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.fallbackTitle": "Solution de secours", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle": "Taille maxi", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.nonTextFieldsTitle": "Champs non textuels", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle": "Brut", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.snippetTitle": "Extrait", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.textFieldsTitle": "Champs de texte", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTitle": "Mise en surbrillance", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTooltip": "Un extrait est une représentation d'une valeur de champ placée dans une séquence d'échappement. Les correspondances de requête sont encapsulées dans des balises pour être mises en surbrillance. La solution de secours recherchera une correspondance d'extrait, mais reviendra à une valeur brute placée dans une séquence d'échappement si elle n'en trouve pas. La plage est comprise entre 20 et 1 000. La valeur par défaut est de 100.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawAriaLabel": "Basculer le champ brut", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTitle": "Brut", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTooltip": "Un champ brut est une représentation exacte d'une valeur de champ. Doit comporter au moins 20 caractères. Par défaut, il s'agit de l'ensemble du champ.", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetAriaLabel": "Basculer l'extrait de texte", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetFallbackAriaLabel": "Basculer l'extrait de secours", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.title": "Paramètres de résultat", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.unsavedChangesMessage": "Les paramètres de résultat n'ont pas été enregistrés. Voulez-vous vraiment quitter ?", - "xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge": "EXEMPLE DE MOTEUR", - "xpack.enterpriseSearch.appSearch.engine.schema.addSchemaErrorMessage": "Le nom du champ existe déjà : {fieldName}", - "xpack.enterpriseSearch.appSearch.engine.schema.addSchemaSuccessMessage": "Nouveau champ ajouté : {fieldName}", - "xpack.enterpriseSearch.appSearch.engine.schema.confirmSchemaButtonLabel": "Confirmer les types", - "xpack.enterpriseSearch.appSearch.engine.schema.conflicts": "Conflits de schémas", - "xpack.enterpriseSearch.appSearch.engine.schema.createSchemaFieldButtonLabel": "Créer un champ de schéma", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel": "Lire le guide du schéma d'indexation", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.description": "Créez des champs de schéma à l'avance, ou indexez quelques documents et un schéma sera créé à votre place.", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.title": "Créer un schéma", - "xpack.enterpriseSearch.appSearch.engine.schema.errors": "Erreurs de modification de schéma", - "xpack.enterpriseSearch.appSearch.engine.schema.hasIncompleteFields": "Le réglage de la précision n'est pas activé sur tous les champs", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.description": "Pour certains champs, il manque un ou plusieurs sous-champs utilisés par App Search. Certaines fonctionnalités de recherche ne fonctionneront peut-être pas tant que ces sous-champs ne seront pas ajoutés.", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.link": "En savoir plus.", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.title": "Il manque des sous-champs pour {count, plural, one {un champ} other {# champs}}", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsDescription": "Champs appartenant à un ou plusieurs moteurs.", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsTitle": "Champs actifs", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.allEngines": "Tout", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutDescription": "Le ou les champs présentent des types de champs incohérents entre les moteurs sources composant ce métamoteur. Appliquez un type de champ cohérent à partir des moteurs sources afin que ces champs deviennent interrogeables.", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutTitle": "{conflictingFieldsCount, plural, one {# champ n'est pas interrogeable} other {# champs ne sont pas interrogeables}}", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.description": "Champs actifs et inactifs, par moteur.", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.fieldTypeConflicts": "Conflits de type champ", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsDescription": "Ces champs présentent des conflits de type. Pour activer ces champs, modifiez les types dans les moteurs sources pour les faire correspondre.", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsTitle": "Champs inactifs", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.title": "Schéma de métamoteur", - "xpack.enterpriseSearch.appSearch.engine.schema.missingSubfieldsLabel": "Sous-champs manquants", - "xpack.enterpriseSearch.appSearch.engine.schema.pageDescription": "Ajoutez de nouveaux champs ou modifiez le type des champs existants.", - "xpack.enterpriseSearch.appSearch.engine.schema.pageTitle": "Gérer le schéma du moteur", - "xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageDescription": "Afficher les types de champs du schéma.", - "xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageTitle": "Schéma de moteur", - "xpack.enterpriseSearch.appSearch.engine.schema.reindexErrorsBreadcrumb": "Réindexer les erreurs", - "xpack.enterpriseSearch.appSearch.engine.schema.reindexJob.title": "Erreurs de modification de schéma", - "xpack.enterpriseSearch.appSearch.engine.schema.title": "Schéma", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFieldLabel": "Récemment ajouté", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields": "Nouveaux champs non confirmés", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.description": "Définissez vos nouveaux champs de schéma sur leurs types corrects ou prévus, puis confirmez vos types de champs.", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.title": "Vous avez récemment ajouté de nouveaux champs de schéma", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.description": "Si ces nouveaux champs doivent être interrogeables, mettez à jour vos paramètres de recherche pour les inclure. Si vous souhaitez qu'ils restent non interrogeables, confirmez vos nouveaux types de champs pour rejeter cette alerte.", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.searchSettingsButtonLabel": "Mettre à jour les paramètres de recherche", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.title": "Par défaut, aucune recherche n'est effectuée dans les champs récemment ajoutés", - "xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaButtonLabel": "Enregistrer les modifications", - "xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaSuccessMessage": "Schéma mis à jour", - "xpack.enterpriseSearch.appSearch.engine.searchUI.bodyDescription": "Search UI (interface utilisateur de recherche) est une bibliothèque gratuite et ouverte qui permet de créer des expériences de recherche avec React. {link}.", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel": "Lire le guide de Search UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.description": "Un schéma sera automatiquement créé à votre place après votre indexation de quelques documents.", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.title": "Ajouter des documents pour générer une Search UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldHelpText": "Valeurs à facettes rendues en tant que filtres et disponibles comme affinement de recherche", - "xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldLabel": "Champs de filtre (facultatif)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.generatePreviewButtonLabel": "Générer une expérience de recherche", - "xpack.enterpriseSearch.appSearch.engine.searchUI.guideLinkText": "En savoir plus sur Search UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.lowerBodyDescription": "Utilisez les champs ci-dessous pour générer un exemple d'expérience de recherche créée avec Search UI. Utilisez l'exemple pour prévisualiser les résultats de recherche ou utilisez-le comme base pour créer votre propre expérience de recherche personnalisée. {link}.", - "xpack.enterpriseSearch.appSearch.engine.searchUI.noSearchKeyErrorMessage": "Il semblerait que vous ne disposiez d'aucune clé de recherche publique ayant accès au moteur \"{engineName}\". Veuillez visiter la page {credentialsTitle} pour en définir une.", - "xpack.enterpriseSearch.appSearch.engine.searchUI.repositoryLinkText": "Afficher le référentiel Github", - "xpack.enterpriseSearch.appSearch.engine.searchUI.sortFieldLabel": "Champs de tri (facultatif)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.sortHelpText": "Utilisé pour afficher les options de tri des résultats, par ordre croissant et décroissant", - "xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldHelpText": "Fournir une URL d'image pour afficher une image miniature", - "xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldLabel": "Champ de miniature (facultatif)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.title": "Search UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldHelpText": "Utilisé comme identifiant visuel du niveau le plus élevé pour tout résultat rendu", - "xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldLabel": "Champ de titre (facultatif)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldHelpText": "Utilisé comme cible de lien d'un résultat, le cas échéant", - "xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldLabel": "Champ d'URL (facultatif)", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesButtonLabel": "Ajouter des moteurs", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.description": "Ajoutez des moteurs supplémentaires à ce métamoteur.", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.title": "Ajouter des moteurs", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesPlaceholder": "Sélectionner un ou plusieurs moteurs", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesSuccessMessage": "{sourceEnginesCount, plural, one {# moteur a été ajouté} other {# moteurs ont été ajoutés}} à ce métamoteur", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.removeSourceEngineSuccessMessage": "Le moteur \"{engineName}\" a été retiré de ce métamoteur", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.title": "Gérer les moteurs", - "xpack.enterpriseSearch.appSearch.engine.suggestedCurationsCallout.description": "Sur la base de l'analytique de votre moteur, de nouvelles curations suggérées sont prêtes à être révisées.", - "xpack.enterpriseSearch.appSearch.engine.suggestedCurationsCallout.title": "Nouvelles curations suggérées à réviser", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSuccessMessage": "Ensemble de synonymes créé", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetButtonLabel": "Créer un ensemble de synonymes", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetTitle": "Ajouter un ensemble de synonymes", - "xpack.enterpriseSearch.appSearch.engine.synonyms.deleteConfirmationMessage": "Voulez-vous vraiment supprimer cet ensemble de synonymes ?", - "xpack.enterpriseSearch.appSearch.engine.synonyms.deleteSuccessMessage": "Ensemble de synonymes supprimé", - "xpack.enterpriseSearch.appSearch.engine.synonyms.description": "Utilisez des synonymes pour créer des liens entre les recherches qui ont contextuellement le même sens dans votre ensemble de données.", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel": "Lire le guide des synonymes", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.description": "Les synonymes créent un lien entre les recherches ayant un contexte ou un sens similaire. Utilisez-les pour orienter les utilisateurs vers un contenu pertinent.", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.title": "Créer votre premier ensemble de synonymes", - "xpack.enterpriseSearch.appSearch.engine.synonyms.iconAriaLabel": "synonyme de", - "xpack.enterpriseSearch.appSearch.engine.synonyms.impactDescription": "Cet ensemble aura bientôt un impact sur vos résultats.", - "xpack.enterpriseSearch.appSearch.engine.synonyms.synonymInputPlaceholder": "Saisir un synonyme", - "xpack.enterpriseSearch.appSearch.engine.synonyms.title": "Synonymes", - "xpack.enterpriseSearch.appSearch.engine.synonyms.updateSuccessMessage": "Ensemble de synonymes mis à jour", - "xpack.enterpriseSearch.appSearch.engine.synonyms.updateSynonymSetTitle": "Gérer l'ensemble de synonymes", - "xpack.enterpriseSearch.appSearch.engine.universalLanguage": "Universel", - "xpack.enterpriseSearch.appSearch.engineAssignmentLabel": "Attribution de moteur", - "xpack.enterpriseSearch.appSearch.engineCreation.configureElasticsearchEngine.callout.title": "Un alias sera créé et utilisé pour ce moteur.", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.errorText": "Il existe déjà un index ou un alias portant le nom {aliasName}. Veuillez choisir un autre nom d'alias.", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.label": "Nom d’alias", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefix.helpText": "Les noms d'alias doivent commencer par 'search-' pour être utilisés par les moteurs App Search", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefixAndNamed.helpText": "Les noms d'alias doivent commencer par 'search-' pour être utilisés par les moteurs App Search. Votre alias sera appelé", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.appSearch.description": "Fournissez un nom unique et un choix de langue facultatif pour votre moteur App Search.", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.backButton.label": "Retour", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.body": "Les moteurs App Search ne peuvent être créés qu'avec des index ou des alias précédés du préfixe \"search-\". Si vous sélectionnez un index qui ne commence pas par \"search-\", alors un alias vers cet index sera créé et utilisé.", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.title": "App Search impose des exigences au niveau des index et des alias", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.continue.label": "Continuer", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.description": "Donnez un nom unique à votre moteur App Search et sélectionnez un index.", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.docCount": "Nombre de documents", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.indexSelectorAriaLabel": "Sélectionnez l'index Elasticsearch que vous souhaitez utiliser", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.selectable.empty": "Aucun index Elasticsearch disponible", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.selectable.loading": "Chargement des index Elasticsearch", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.status": "Statut", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.storage": "Taille du stockage", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.searchIndexSelectable.helpText": "Sélectionnez un index ou un alias commençant par 'search-' ou créez un nouvel alias ci-dessous", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.searchIndexSelectable.label": "Sélectionnez l'index Elasticsearch que vous souhaitez utiliser", - "xpack.enterpriseSearch.appSearch.engineCreation.form.backButton.label": "Retour", - "xpack.enterpriseSearch.appSearch.engineCreation.form.continue.label": "Créer un moteur de recherche", - "xpack.enterpriseSearch.appSearch.engineCreation.form.editConfiguration.label": "Modifier la configuration", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineLanguage.label": "Langue du moteur", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.allowedCharactersHelpText": "Les noms de moteurs ne peuvent contenir que des caractères minuscules, des chiffres et des tirets", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.label": "Nom du moteur", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.placeholder": "par exemple, mon-moteur-de-recherche", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.sanitizedNameHelpText": "Votre moteur sera nommé", - "xpack.enterpriseSearch.appSearch.engineCreation.form.submitButton.buttonLabel": "Créer un moteur", - "xpack.enterpriseSearch.appSearch.engineCreation.form.title": "Configurer votre moteur de recherche", - "xpack.enterpriseSearch.appSearch.engineCreation.nextStep.buttonLabel": "Continuer", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.aliasName.title": "Nom d’alias", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.description": "Votre moteur App Search sera créé avec la configuration suivante.", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.elasticsearchIndex.title": "Index Elasticsearch", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineName.title": "Nom du moteur", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.description": "Basé sur un index Elasticsearch", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.title": "Type de moteur", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.title.label": "Vérifier votre moteur de recherche", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.appSearch.description": "Utilisez des API App Search pour gérer vos documents. App Search inscrira vos documents dans un index sous-jacent et le gérera pour vous.", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.appSearch.title": "Documents gérés par App Search", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.elasticsearch.description": "Utilisez un index existant pour gérer vos documents. Ajoute la recherche avec App Search aux indices Elasticsearch. Certaines fonctions nécessitent des champs secondaires spécifiques.", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.elasticsearch.title": "Basé sur un index Elasticsearch", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description": "Vous pouvez désormais créer des moteurs de recherche qui utilisent un index Elasticsearch existant pour combiner les outils de gestion de recherche d'App Search avec la flexibilité des index Elasticsearch.", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description.link": "En savoir plus", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.title": "Sélectionner un type de moteur de recherche", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label": "Configuration", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.finish.label": "Terminer", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.review.label": "Révision", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label": "Type de moteur de recherche", - "xpack.enterpriseSearch.appSearch.engineCreation.successMessage": "Le moteur \"{name}\" a été créé", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.chineseDropDownOptionLabel": "Chinois", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.danishDropDownOptionLabel": "Danois", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.dutchDropDownOptionLabel": "Néerlandais", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.englishDropDownOptionLabel": "Anglais", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.frenchDropDownOptionLabel": "Français", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.germanDropDownOptionLabel": "Allemand", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.italianDropDownOptionLabel": "Italien", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.japaneseDropDownOptionLabel": "Japonais", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.koreanDropDownOptionLabel": "Coréen", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseBrazilDropDownOptionLabel": "Portugais (Brésil)", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseDropDownOptionLabel": "Portugais", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.russianDropDownOptionLabel": "Russe", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.spanishDropDownOptionLabel": "Espagnol", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.thaiDropDownOptionLabel": "Thaï", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.universalDropDownOptionLabel": "Universel", - "xpack.enterpriseSearch.appSearch.engineCreation.title": "Créer un moteur", - "xpack.enterpriseSearch.appSearch.engineRequiredError": "Au moins un moteur attribué est requis.", - "xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsButtonLabel": "Actualiser", - "xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsMessage": "De nouveaux événements ont été consignés dans un log.", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.closeButton": "Fermer", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.eventTip": "Afficher les événements des dernières 24 heures", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.eventCategory": "Catégorie d'événement", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.eventType": "Type d'événement", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.outcome": "Résultat", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.updatedBy": "Mis à jour par", - "xpack.enterpriseSearch.appSearch.engines.createEngineButtonLabel": "Créer un moteur", - "xpack.enterpriseSearch.appSearch.engines.createMetaEngineButtonLabel": "Créer un métamoteur", - "xpack.enterpriseSearch.appSearch.engines.curations.refreshButton": "Actualiser", - "xpack.enterpriseSearch.appSearch.engines.curations.rejectedCurationsHistoryPanel.refresh": "Actualiser", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptButtonLabel": "En savoir plus sur les métamoteurs", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptDescription": "Les métamoteurs vous permettent de combiner plusieurs moteurs dans un même moteur interrogeable.", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptTitle": "Créer votre premier métamoteur", - "xpack.enterpriseSearch.appSearch.engines.metaEnginesTable.fieldTypeConflictWarning": "Conflit de type champ", - "xpack.enterpriseSearch.appSearch.engines.metaEnginesTable.sourceEnginesCount": "{sourceEnginesCount, plural, one {# moteur} other {# moteurs}}", - "xpack.enterpriseSearch.appSearch.engines.title": "Moteurs", - "xpack.enterpriseSearch.appSearch.enginesOverview.metaEnginesTable.sourceEngines.title": "Moteurs sources", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.buttonDescription": "Supprimer ce moteur", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.confirmationPopupMessage": "Voulez-vous vraiment supprimer définitivement \"{engineName}\" et tout son contenu ?", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.successMessage": "Le moteur \"{engineName}\" a été supprimé", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage.buttonDescription": "Gérer ce moteur", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions": "Actions", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt": "Créé à", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount": "Nombre de documents", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount": "Nombre de champs", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.language": "Langue", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.lastUpdated": "Dernière mise à jour", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name": "Nom", - "xpack.enterpriseSearch.appSearch.enginesOverview.title": "Aperçu des moteurs", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.description": "En envoyant vos commentaires, vous reconnaissez que vous avez lu et que vous acceptez nos {termsOfService}, et vous autorisez Elastic à vous {contact} concernant des produits et services similaires en utilisant les informations que vous avez fournies plus haut. Pour en savoir plus ou pour vous désinscrire à tout moment, consultez {privacyStatementLink}.", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.Label": "Souhaitez-vous nous faire part d'autres commentaires ?", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.optionalLabel": "Facultatif", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureButtonLabel": "Ajouter des analyses de recherche", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureDescription": "Ajoutez et affichez des analyses et des logs pour votre application de recherche", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureName": "Analyses et logs de recherche", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.panelText": "Vous pouvez suivre et analyser le comportement de recherche et de clic des utilisateurs avec Behavioral Analytics. Instrumentez votre site Web ou votre application pour suivre les actions pertinentes des utilisateurs.", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureButtonLabel": "Sécuriser avec Elasticsearch", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureDescription": "Gérez vos utilisateurs et vos rôles, ainsi que les informations d'identification pour accéder à vos points de terminaison de recherche", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureName": "Informations d'identification et rôles", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.panelText": "Elasticsearch fournit un ensemble complet de fonctionnalités de sécurité, notamment la sécurité au niveau des documents et le contrôle d'accès basé sur les rôles.", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureButtonLabel": "Utiliser les règles des requêtes", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureDescription": "Organisez et épinglez les résultats pour des requêtes spécifiques", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureName": "Résultats pertinents", - "xpack.enterpriseSearch.appSearch.gateForm.curations.panelText": "Les règles de requête fournissent un ensemble d'outils davantage robustes pour personnaliser vos résultats de recherche pour les requêtes correspondant à des critères et des métadonnées spécifiques.", - "xpack.enterpriseSearch.appSearch.gateForm.description": "Le produit autonome App Search reste disponible en mode maintenance, mais il n'est pas recommandé pour de nouvelles expériences de recherche. Nous vous recommandons d'utiliser des outils Elasticsearch natifs, qui offrent à la fois de la flexibilité et de possibilité d'interaction, et incluent de nouvelles fonctionnalités de recherche intéressantes. Pour vous aider à choisir l'outil le mieux adapté à votre cas d'utilisation, nous avons créé cet assistant de recommandation. Sélectionnez les fonctionnalités dont vous avez besoin, nous vous orienterons alors vers les fonctionnalités Elasticsearch correspondantes. Si vous souhaitez toujours utiliser le produit App Search autonome, vous pouvez le faire après avoir soumis le formulaire.", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.learnMore": "En savoir plus", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.subTitle": "En fonction de votre sélection, nous vous recommandons :", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.title": "Équivalent natif d'Elasticsearch", - "xpack.enterpriseSearch.appSearch.gateForm.featureOther.Label": "Pouvez-vous expliquer quelles autres fonctionnalités vous recherchez ?", - "xpack.enterpriseSearch.appSearch.gateForm.features.Label": "Quelle fonctionnalité d'App Search souhaitez-vous utiliser ?", - "xpack.enterpriseSearch.appSearch.gateForm.features.selectOption": "Sélectionner une option", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label": "Voulez-vous rejoindre nos travaux de recherche en tant qu'utilisateur pour améliorer Elasticsearch ?", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.No": "Non", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.Yes": "Oui", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.optionalLabel": "Facultatif", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureButtonLabel": "Ajuster la pertinence de la recherche", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureDescription": "Ajustez la pertinence de vos résultats à l'aide de méthodes de classement et d'optimisation", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureName": "Réglage de la pertinence", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.panelText": "Le DSL de requête d'Elasticsearch fournit un ensemble approfondi d'outils de réglage de la pertinence.", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureButtonLabel": "Construire une expérience de recherche avec Search UI", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureDescription": "Utilisez des interfaces utilisateur graphiques (GUI) pour gérer l'expérience de l'application de recherche", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureName": "Interfaces de recherche et de gestion", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.panelText": "L'interface utilisateur de recherche fournit les composants nécessaires pour créer une expérience de recherche moderne.", - "xpack.enterpriseSearch.appSearch.gateForm.submit": "Envoyer", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureButtonLabel": "Rechercher à l'aide de synonymes", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureDescription": "Effectuer une recherche avec une expansion de requête basée sur des synonymes", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureName": "Rechercher à l'aide de synonymes", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.panelText": "Utilisez les API Elasticsearch Synonyms pour créer et gérer facilement des ensembles de synonymes.", - "xpack.enterpriseSearch.appSearch.gateForm.title": "Avant de commencer...", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureButtonLabel": "Essayez Open Crawler", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureDescription": "Ingérer du contenu Web dans Elasticsearch à l'aide d'un robot d'indexation", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureName": "Robot d'indexation", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.panelText": "Saviez-vous que le nouveau robot d'indexation Open Crawler autogéré d'Elastic est désormais disponible ? Vous pouvez maintenir votre contenu Web synchronisé avec vos index optimisés pour la recherche !", - "xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsDetail": "Pour gérer les analyses et le logging, {visitSettingsLink}.", - "xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsLinkText": "accédez à vos paramètres", - "xpack.enterpriseSearch.appSearch.logRetention.callout.disabledSinceTitle": "{logsTitle} ont été désactivés depuis le {disabledDate}.", - "xpack.enterpriseSearch.appSearch.logRetention.callout.disabledTitle": "{logsTitle} ont été désactivés.", - "xpack.enterpriseSearch.appSearch.logRetention.customPolicy": "Vous disposez d'une politique de conservation des logs {logsType} personnalisée.", - "xpack.enterpriseSearch.appSearch.logRetention.defaultPolicy": "Vos logs {logsType} sont stockés pendant au moins {minAgeDays, plural, one {# jour} other {# jours}}.", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging": "Le logging {logsType} a été désactivé pour tous les moteurs.", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging.collected": "La dernière date de collecte de logs {logsType} était le {disabledAtDate}.", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging.notCollected": "Aucun log {logsType} n'a été collecté.", - "xpack.enterpriseSearch.appSearch.logRetention.tooltip": "Informations de conservation des logs", - "xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.capitalized": "Analyse", - "xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.lowercase": "analyse", - "xpack.enterpriseSearch.appSearch.logRetention.type.api.title.capitalized": "API", - "xpack.enterpriseSearch.appSearch.logRetention.type.api.title.lowercase": "API", - "xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.capitalized": "Audit", - "xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.lowercase": "audit", - "xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.capitalized": "Robot d'indexation", - "xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.lowercase": "robot d'indexation", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationDescription": "{documentationLink} pour en savoir plus sur la mise en route.", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationLink": "Lire la documentation", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.allowedCharactersHelpText": "Les noms de métamoteurs ne peuvent contenir que des caractères minuscules, des chiffres et des tirets", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.label": "Nom du métamoteur", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.placeholder": "par exemple, mon-métamoteur", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.sanitizedNameHelpText": "Votre métamoteur sera nommé", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.metaEngineDescription": "Les métamoteurs vous permettent de combiner plusieurs moteurs dans un même moteur interrogeable.", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.label": "Ajouter des moteurs sources à ce métamoteur", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.maxSourceEnginesWarningTitle": "Les métamoteurs ont une limite de {maxEnginesPerMetaEngine} moteurs sources", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.submitButton.buttonLabel": "Créer un métamoteur", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.title": "Nommer votre métamoteur", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.successMessage": "Le métamoteur \"{name}\" a été créé", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.title": "Créer un métamoteur", - "xpack.enterpriseSearch.appSearch.metaEngines.title": "Métamoteurs", - "xpack.enterpriseSearch.appSearch.multiInputRows.addValueButtonLabel": "Ajouter une valeur", - "xpack.enterpriseSearch.appSearch.multiInputRows.inputRowPlaceholder": "Entrer une valeur", - "xpack.enterpriseSearch.appSearch.ownerRoleTypeDescription": "Les propriétaires peuvent tout faire. Le compte peut avoir plusieurs propriétaires, mais il doit toujours y avoir au moins un propriétaire.", - "xpack.enterpriseSearch.appSearch.productCardDescription": "Une solution sur mesure pour les applications et les sites web, qui fournit les outils dont vous avez besoin pour concevoir, implémenter et gérer efficacement ces expériences de recherche orientées clients.", - "xpack.enterpriseSearch.appSearch.productDescription": "Tirez parti des tableaux de bord, des analyses et des API pour simplifier la recherche d'applications avancée.", - "xpack.enterpriseSearch.appSearch.productName": "App Search", - "xpack.enterpriseSearch.appSearch.result.clicks": "{clicks} Clics", - "xpack.enterpriseSearch.appSearch.result.documentDetailLink": "Accéder aux détails des documents", - "xpack.enterpriseSearch.appSearch.result.hideAdditionalFields": "Masquer les champs supplémentaires", - "xpack.enterpriseSearch.appSearch.result.resultPositionLabel": "#{resultPosition}", - "xpack.enterpriseSearch.appSearch.result.showAdditionalFields": "Afficher {numberOfAdditionalFields, number} {numberOfAdditionalFields, plural, one {champ supplémentaire} other {champs supplémentaires}}", - "xpack.enterpriseSearch.appSearch.result.title": "Document {id}", - "xpack.enterpriseSearch.appSearch.roleMappingCreatedMessage": "Votre mapping de rôles a été créé", - "xpack.enterpriseSearch.appSearch.roleMappingDeletedMessage": "Votre mapping de rôles a été supprimé", - "xpack.enterpriseSearch.appSearch.roleMappingsEngineAccessHeading": "Accès au moteur", - "xpack.enterpriseSearch.appSearch.roleMappingUpdatedMessage": "Votre mapping de rôles a été mis à jour", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.buttonLabel": "Essayer un exemple de moteur", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.description": "Testez un moteur avec des exemples de données.", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.title": "Vous voulez simplement essayer ?", - "xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label": "Consigner les événements d'analyse", - "xpack.enterpriseSearch.appSearch.settings.logRetention.api.label": "Consigner les événements d'API", - "xpack.enterpriseSearch.appSearch.settings.logRetention.audit.label": "Consigner les événements d'audit", - "xpack.enterpriseSearch.appSearch.settings.logRetention.crawler.label": "Logs du robot d'indexation", - "xpack.enterpriseSearch.appSearch.settings.logRetention.description": "La conservation des logs est déterminée par les politiques de gestion de cycle de vie des index de votre déploiement.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore": "Découvrez plus d'informations sur la conservation des logs pour Enterprise Search.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.description": "Lorsque vous désactivez l'écriture, les moteurs arrêtent de consigner les événements d'analyse. Vos données existantes sont supprimées en fonction du délai de stockage.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.subheading": "Vos logs d'analyse sont actuellement stockés pendant {minAgeDays} jours.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.title": "Désactiver les écritures d'analyse", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.description": "Lorsque vous désactivez l'écriture, les moteurs arrêtent de consigner les événements d'API. Vos données existantes sont supprimées en fonction du délai de stockage.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.subheading": "Vos logs d'API sont actuellement stockés pendant {minAgeDays} jours.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.title": "Désactiver les écritures d'API", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.description": "Lorsque vous désactivez l'écriture, les moteurs arrêtent de consigner les événements d'audit. Vos données existantes sont supprimées en fonction du délai de stockage.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.subheading": "Vos logs d'audit sont actuellement stockés pendant {minAgeDays} jours.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.title": "Désactiver les écritures d'audit", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.description": "Lorsque vous désactivez l'écriture, les moteurs arrêtent de consigner les événements du robot d'indexation. Vos données existantes sont supprimées en fonction du délai de stockage.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.subheading": "Vos logs du robot d'indexation sont actuellement stockés pendant {minAgeDays} jours.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.title": "Désactiver l'écriture du robot d'indexation", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.disable": "DÉSACTIVER", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.prompt": "Tapez \"{target}\" pour confirmer.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.recovery": "Vous ne pouvez pas récupérer les données supprimées.", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.save": "Enregistrer le paramètre", - "xpack.enterpriseSearch.appSearch.settings.logRetention.title": "Conservation des logs", - "xpack.enterpriseSearch.appSearch.settings.title": "Paramètres", - "xpack.enterpriseSearch.appSearch.setupGuide.description": "Obtenez les outils nécessaires pour concevoir et déployer une recherche puissante pour vos sites web et vos applications mobiles.", - "xpack.enterpriseSearch.appSearch.setupGuide.notConfigured": "App Search n'est pas encore configuré dans votre instance Kibana.", - "xpack.enterpriseSearch.appSearch.setupGuide.videoAlt": "Premiers pas avec App Search : dans cette courte vidéo, nous allons vous aider à configurer App Search et vous expliquer son fonctionnement", - "xpack.enterpriseSearch.appSearch.sourceEngines.removeEngineButton.label": "Retirer du métamoteur", - "xpack.enterpriseSearch.appSearch.sourceEngines.removeEngineConfirmDialogue.description": "Le moteur \"{engineName}\" sera retiré de ce métamoteur. Tous les paramètres existants seront perdus. Voulez-vous vraiment continuer ?", - "xpack.enterpriseSearch.appSearch.specificEnginesDescription": "Effectuez une attribution à un ensemble sélectionné de moteurs de façon statique.", - "xpack.enterpriseSearch.appSearch.specificEnginesLabel": "Attribuer à des moteurs spécifiques", - "xpack.enterpriseSearch.appSearch.tokens.admin.description": "Les clés d'administration privées sont utilisées pour interagir avec l'API des informations d'identification.", - "xpack.enterpriseSearch.appSearch.tokens.admin.name": "Clé d'administration privée", - "xpack.enterpriseSearch.appSearch.tokens.created": "La clé d'API \"{name}\" a été créée", - "xpack.enterpriseSearch.appSearch.tokens.deleted": "La clé d'API \"{name}\" a été supprimée", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.all": "tous", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.readonly": "lecture seule", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.readwrite": "lecture/écriture", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.search": "recherche", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.writeonly": "écriture seule", - "xpack.enterpriseSearch.appSearch.tokens.private.description": "Les clés d'API privées sont utilisées pour l'accès en lecture et/ou en écriture à un ou plusieurs moteurs.", - "xpack.enterpriseSearch.appSearch.tokens.private.name": "Clé d'API privée", - "xpack.enterpriseSearch.appSearch.tokens.search.description": "Les clés de recherche publiques sont utilisées uniquement pour les points de terminaison de recherche.", - "xpack.enterpriseSearch.appSearch.tokens.search.name": "Clé de recherche publique", - "xpack.enterpriseSearch.appSearch.tokens.update": "La clé d'API \"{name}\" a été mise à jour", "xpack.enterpriseSearch.attachIndexBox.createSameIndexButtonLabel": "Créer et attacher un index nommé {indexName}", "xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexErrorTextLabel": "Vous ne pouvez pas créer un nouvel index en utilisant un nom d'index existant. Choisissez un index existant ou créez un nouvel index avec un nouveau nom.", "xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexHelpTextLabel": "Vous pouvez utiliser un index existant ou en créer un nouveau.", @@ -18028,7 +17114,6 @@ "xpack.enterpriseSearch.crawler.startCrawlContextMenu.reapplyCrawlRulesMenuLabel": "Réappliquer les règles d'indexation", "xpack.enterpriseSearch.crawler.urlComboBox.invalidUrlErrorMessage": "Veuillez entrer une URL valide", "xpack.enterpriseSearch.crawlerEmptyState.h2.createYourFirstWebLabel": "Créer votre premier robot d'indexation", - "xpack.enterpriseSearch.crawlerEmptyState.newWebCrawlerButtonLabel": "Nouveau robot d'indexation", "xpack.enterpriseSearch.crawlerEmptyState.openSourceCrawlerButtonLabel": "Code source", "xpack.enterpriseSearch.crawlerEmptyState.p.discoverExtractAndIndexLabel": "Découvrir, extraire et indexer du contenu interrogeable provenant de sites web et de bases de connaissances", "xpack.enterpriseSearch.crawlers.title": "Robot d'indexation Elasticsearch", @@ -18088,8 +17173,6 @@ "xpack.enterpriseSearch.createConnector.startStep.p.whereDoYouWantLabel": "Où souhaitez-vous stocker le connecteur et comment souhaitez-vous le gérer ?", "xpack.enterpriseSearch.createConnector.startStep.p.youWillStartTheLabel": "Vous devrez démarrer le processus de création manuelle d'un nouvel index, d'une clé d'API et d'un ID de connecteur de robot d'indexation. Cela est facultatif, mais vous pouvez également utiliser votre propre configuration.", "xpack.enterpriseSearch.createConnector.startStep.startLabel": "Début", - "xpack.enterpriseSearch.curations.settings.licenseUpgradeLink": "En savoir plus sur les mises à niveau incluses dans la licence", - "xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel": "Démarrer un essai gratuit de 30 jours", "xpack.enterpriseSearch.defaultSettingsFlyout.body.description.ingestPipelinesLink.link": "pipelines d'ingestion", "xpack.enterpriseSearch.defaultSettingsFlyout.body.description.label": "Ces paramètres s'appliquent à tous les nouveaux index Elasticsearch créés par des mécanismes d'ingestion Search. Pour les index basés sur l'ingestion d'API, n'oubliez pas d'inclure le pipeline lorsque vous ingérez des documents. Ces fonctionnalités sont alimentées par {link}", "xpack.enterpriseSearch.defaultSettingsFlyout.callout.body": "Vous pouvez également activer ou désactiver cette fonctionnalité pour un index spécifique sur la page de configuration de l'index.", @@ -18112,8 +17195,6 @@ "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "Effectuez des recherches sur tout, partout. Offrez à vos équipes débordées une expérience de recherche innovante et puissante facilement mise en œuvre. Intégrez rapidement une fonction de recherche préréglée à votre site web, à votre application ou à votre lieu de travail. Effectuez des recherches simples sur tout.", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "Enterprise Search n'est pas encore configuré dans votre instance Kibana.", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "Premiers pas avec Enterprise Search", - "xpack.enterpriseSearch.enterpriseSearchCard.cta": "En savoir plus", - "xpack.enterpriseSearch.entSearch.productCardDescription": "Applications standalone adaptées à des expériences de recherche plus simples, conviviales et axées sur les entreprises.", "xpack.enterpriseSearch.exampleConnectorLabel": "Exemple", "xpack.enterpriseSearch.finishUpStep.euiButton.viewInDiscoverLabel": "Afficher dans Discover", "xpack.enterpriseSearch.getConnectorTypeBadge.connectorClientBadgeLabel": "Autogéré", @@ -18194,8 +17275,6 @@ "xpack.enterpriseSearch.ingestSelector.method.connectorButtonLabel": "Créer un connecteur", "xpack.enterpriseSearch.ingestSelector.method.connectors": "Connecteurs", "xpack.enterpriseSearch.ingestSelector.method.connectors.description": "Extraire, transformer, indexer et synchroniser des données issues d'une source de données tiers.", - "xpack.enterpriseSearch.ingestSelector.method.crawler": "Robot d'indexation", - "xpack.enterpriseSearch.ingestSelector.method.crawler.description": "Découvrir, extraire et indexer du contenu interrogeable provenant de sites web et de bases de connaissances.", "xpack.enterpriseSearch.ingestSelector.method.fileUpload": "Charger un fichier", "xpack.enterpriseSearch.ingestSelector.method.fileUpload.description": "Fichiers texte délimités, tels que CSV et TSV, JSON délimité par une nouvelle ligne.", "xpack.enterpriseSearch.ingestSelector.method.fileUploadLabel": "Choisir un fichier", @@ -18204,12 +17283,9 @@ "xpack.enterpriseSearch.ingestSelector.method.languageClients.description": "Découvrez tous les clients de langage que nous prenons en charge et comment les utiliser avec notre API.", "xpack.enterpriseSearch.ingestSelector.method.sampleData": "Exemple de données", "xpack.enterpriseSearch.ingestSelector.method.sampleData.description": "Utilisez nos exemples de données interrogeables pour essayer une expérience de recherche.", - "xpack.enterpriseSearch.ingestSelector.method.sourceCodeButtonLabel": "Code source", "xpack.enterpriseSearch.inlineEditableTable.newRowButtonLabel": "Nouvelle ligne", "xpack.enterpriseSearch.integrations.apiDescription": "Ajouter la recherche à votre application avec les API robustes d'Elasticsearch.", "xpack.enterpriseSearch.integrations.apiName": "API", - "xpack.enterpriseSearch.integrations.webCrawlerDescription": "Ajoutez la recherche à votre site web avec le robot d'indexation.", - "xpack.enterpriseSearch.integrations.webCrawlerName": "Robot d'indexation", "xpack.enterpriseSearch.invalidJsonError": "JSON non valide", "xpack.enterpriseSearch.languages.cURL": "cURL", "xpack.enterpriseSearch.languages.cURL.githubLink": "curl", @@ -18252,7 +17328,6 @@ "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "Planification", "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "Règles de synchronisation", "xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "Applications de recherche", - "xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "Moteurs", "xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "Connecteurs", "xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "Index", "xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "Robots d'indexation", @@ -18299,84 +17374,9 @@ "xpack.enterpriseSearch.productSelector.overview.description": "La première étape pour créer votre expérience de recherche consiste à créer un index Elasticsearch optimisé pour la recherche et à y importer votre contenu. Elasticsearch propose plusieurs options conviviales parmi lesquelles choisir et qui s'ajustent au mieux à votre expertise technique et à vos sources de données.", "xpack.enterpriseSearch.productSelector.overview.title": "Ingérer votre contenu", "xpack.enterpriseSearch.productSelectorCalloutTitle": "Mettez à niveau pour obtenir des fonctionnalités de niveau entreprise pour votre équipe", - "xpack.enterpriseSearch.readOnlyMode.warning": "Enterprise Search est en mode de lecture seule. Vous ne pourrez pas effectuer de changements tels que création, modification ou suppression.", "xpack.enterpriseSearch.required": "Obligatoire", "xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel": "Vérifier les exigences", "xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel": "Consultez la documentation de ce connecteur pour en apprendre plus sur les prérequis de connexion à {serviceType} ainsi que les prérequis de configuration.", - "xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "Ajouter un mapping", - "xpack.enterpriseSearch.roleMapping.addUserLabel": "Ajouter un utilisateur", - "xpack.enterpriseSearch.roleMapping.allLabel": "Tout", - "xpack.enterpriseSearch.roleMapping.anyAuthProviderLabel": "Tout fournisseur d'authentification actuel ou futur", - "xpack.enterpriseSearch.roleMapping.attributeSelectorTitle": "Mapping d'attribut", - "xpack.enterpriseSearch.roleMapping.attributeValueLabel": "Valeur d'attribut", - "xpack.enterpriseSearch.roleMapping.deactivatedLabel": "Désactivé", - "xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutDescription": "Cet utilisateur n'est pas actif actuellement, et l'accès a été temporairement révoqué. Les utilisateurs peuvent être réactivés via la zone de gestion des utilisateurs de la console Kibana.", - "xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutLabel": "Utilisateur désactivé", - "xpack.enterpriseSearch.roleMapping.deleteRoleMappingDescription": "Veuillez noter que la suppression d'un mapping est permanente et qu'elle ne peut pas être annulée", - "xpack.enterpriseSearch.roleMapping.emailLabel": "E-mail", - "xpack.enterpriseSearch.roleMapping.enableRolesButton": "Activer l'accès basé sur les rôles", - "xpack.enterpriseSearch.roleMapping.enableRolesLink": "En savoir plus sur l'accès basé sur les rôles", - "xpack.enterpriseSearch.roleMapping.enableUsersLink": "En savoir plus sur la gestion des utilisateurs", - "xpack.enterpriseSearch.roleMapping.enginesLabel": "Moteurs", - "xpack.enterpriseSearch.roleMapping.existingInvitationLabel": "L'utilisateur n'a pas encore accepté l'invitation.", - "xpack.enterpriseSearch.roleMapping.existingUserLabel": "Ajouter un utilisateur existant", - "xpack.enterpriseSearch.roleMapping.externalAttributeLabel": "Attribut externe", - "xpack.enterpriseSearch.roleMapping.externalAttributeTooltip": "Les attributs externes sont définis par le fournisseur d'identité et varient d'un service à l'autre.", - "xpack.enterpriseSearch.roleMapping.filterRoleMappingsPlaceholder": "Filtrer les mappings de rôles", - "xpack.enterpriseSearch.roleMapping.filterUsersLabel": "Filtrer les utilisateurs", - "xpack.enterpriseSearch.roleMapping.flyoutCreateTitle": "Créer un mapping de rôles", - "xpack.enterpriseSearch.roleMapping.flyoutDescription": "Attribuer des rôles et des autorisations basées sur les attributs de l'utilisateur", - "xpack.enterpriseSearch.roleMapping.flyoutUpdateTitle": "Mettre à jour un mapping de rôles", - "xpack.enterpriseSearch.roleMapping.groupsLabel": "Groupes", - "xpack.enterpriseSearch.roleMapping.individualAuthProviderLabel": "Sélectionner des fournisseurs d'authentification individuels", - "xpack.enterpriseSearch.roleMapping.invitationDescription": "Cette URL peut être partagée avec l'utilisateur pour lui permettre d'accepter l'invitation d'Enterprise Search et de définir un nouveau mot de passe", - "xpack.enterpriseSearch.roleMapping.invitationLink": "Lien d'invitation d'Enterprise Search", - "xpack.enterpriseSearch.roleMapping.invitationPendingLabel": "Invitation en attente", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningDescription": "Envisagez de leur donner le rôle \"utilisateur-enterprise-search\".", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningErrorMessage": "Cet utilisateur d'Elasticsearch n'a pas de rôle Enterprise Search dans Elasticsearch. Il n'a peut-être pas accès à Kibana.", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningTitle": "Avertissement d'accès à Kibana", - "xpack.enterpriseSearch.roleMapping.manageRoleMappingTitle": "Gérer le mapping de rôles", - "xpack.enterpriseSearch.roleMapping.newInvitationLabel": "URL d'invitation", - "xpack.enterpriseSearch.roleMapping.newRoleMappingTitle": "Ajouter un mapping de rôles", - "xpack.enterpriseSearch.roleMapping.newUserDescription": "Fournir un accès et des autorisations granulaires", - "xpack.enterpriseSearch.roleMapping.newUserLabel": "Créer un nouvel utilisateur", - "xpack.enterpriseSearch.roleMapping.noResults.message": "Impossible de trouver des mappings de rôles correspondants", - "xpack.enterpriseSearch.roleMapping.notFoundMessage": "Impossible de trouver un mapping de rôles correspondant.", - "xpack.enterpriseSearch.roleMapping.noUsersDescription": "Il est possible d'ajouter des utilisateurs individuellement, pour un maximum de flexibilité. Les mappings de rôles fournissent une interface plus large pour ajouter un grand nombre d'utilisateurs à l'aide des attributs d'utilisateur.", - "xpack.enterpriseSearch.roleMapping.noUsersLabel": "Impossible de trouver les utilisateurs correspondants", - "xpack.enterpriseSearch.roleMapping.noUsersTitle": "Aucun utilisateur ajouté", - "xpack.enterpriseSearch.roleMapping.rbacButtonDisabledLabel": "L'activation de RBAC peut être effectuée par un superutilisateur.", - "xpack.enterpriseSearch.roleMapping.removeRoleMappingButton": "Retirer le mapping", - "xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle": "Retirer le mapping de rôles", - "xpack.enterpriseSearch.roleMapping.removeUserButton": "Retirer l'utilisateur", - "xpack.enterpriseSearch.roleMapping.requiredLabel": "Requis", - "xpack.enterpriseSearch.roleMapping.roleLabel": "Rôle", - "xpack.enterpriseSearch.roleMapping.roleMappingFlyoutCreateButton": "Créer un mapping", - "xpack.enterpriseSearch.roleMapping.roleMappingFlyoutUpdateButton": "Mettre à jour le mapping", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingButton": "Créer un nouveau mapping de rôles", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDescription": "Les mappings de rôles fournissent une interface qui permet d'associer les attributs de rôle natifs ou régis par SAML à des autorisations {productName}.", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDocsLink": "Découvrez plus d'informations sur les mappings de rôles.", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingTitle": "Mappings de rôles", - "xpack.enterpriseSearch.roleMapping.roleMappingsTitle": "Utilisateurs et rôles", - "xpack.enterpriseSearch.roleMapping.roleModalText": "La suppression d'un mapping de rôle pourrait révoquer l'accès à l'utilisateur actuellement connecté. Avant de poursuivre, vérifiez que l'utilisateur actuellement connecté dispose du niveau d'accès approprié via un mapping de rôle différent afin d'éviter tout comportement indésirable. Cette action peut ne pas prendre effet immédiatement pour les rôles gérés par SAML. Les utilisateurs ayant une session SAML active conserveront l'accès jusqu'à l'expiration de cette dernière.", - "xpack.enterpriseSearch.roleMapping.rolesDisabledDescription": "Tous les utilisateurs définis pour ce déploiement disposent actuellement d'un accès total à {productName}. Pour limiter les accès et gérer les autorisations, vous devez activer l'accès basé sur les rôles pour Enterprise Search.", - "xpack.enterpriseSearch.roleMapping.rolesDisabledNote": "Remarque : l'activation de l'accès basé sur les rôles limite les accès aussi bien pour App Search que pour Workplace Search. Une fois l'activation effectuée, vérifiez la gestion des accès pour les deux produits, le cas échéant.", - "xpack.enterpriseSearch.roleMapping.rolesDisabledTitle": "L'accès basé sur les rôles est désactivé", - "xpack.enterpriseSearch.roleMapping.saveRoleMappingButtonLabel": "Enregistrer le mapping de rôles", - "xpack.enterpriseSearch.roleMapping.smtpCalloutLabel": "Des invitations personnalisées seront envoyées automatiquement lorsqu'une", - "xpack.enterpriseSearch.roleMapping.smtpLinkLabel": "configuration SMTP Enterprise Search sera fournie", - "xpack.enterpriseSearch.roleMapping.updateRoleMappingButtonLabel": "Mettre à jour un mapping de rôles", - "xpack.enterpriseSearch.roleMapping.updateUserDescription": "Gérer un accès et des autorisations granulaires", - "xpack.enterpriseSearch.roleMapping.updateUserLabel": "Mettre à jour l'utilisateur", - "xpack.enterpriseSearch.roleMapping.userAddedLabel": "Utilisateur ajouté", - "xpack.enterpriseSearch.roleMapping.userModalText": "La suppression d'un utilisateur révoque immédiatement l'accès à l'expérience, sauf si les attributs de cet utilisateur correspondent également à un mapping de rôles pour l'authentification native ou régie par SAML. Dans ce cas, les mappings de rôles associés doivent également être vérifiés et ajustés, le cas échéant.", - "xpack.enterpriseSearch.roleMapping.userModalTitle": "Retirer {username}", - "xpack.enterpriseSearch.roleMapping.usernameLabel": "Nom d'utilisateur", - "xpack.enterpriseSearch.roleMapping.usernameNoUsersText": "Aucun utilisateur existant n'est éligible pour un ajout.", - "xpack.enterpriseSearch.roleMapping.usersHeadingDescription": "La gestion des utilisateurs fournit un accès granulaire pour répondre aux besoins d'autorisations spéciales ou individuelles. Certains utilisateurs peuvent être exclus de cette liste. Ce sont notamment les utilisateurs de sources fédérées, telles que SAML, qui sont gérées par des mappings de rôles, et les comptes utilisateurs intégrés tels que les utilisateurs \"elastic\" ou \"enterprise_search\".", - "xpack.enterpriseSearch.roleMapping.usersHeadingLabel": "Ajouter un nouvel utilisateur", - "xpack.enterpriseSearch.roleMapping.usersHeadingTitle": "Utilisateurs", - "xpack.enterpriseSearch.roleMapping.userUpdatedLabel": "Utilisateur mis à jour", "xpack.enterpriseSearch.save": "Enregistrer", "xpack.enterpriseSearch.schema.addFieldModal.addFieldButtonLabel": "Ajouter un champ", "xpack.enterpriseSearch.schema.addFieldModal.description": "Une fois qu'un champ a été ajouté à votre schéma, il n'est plus possible de le retirer.", @@ -18588,9 +17588,7 @@ "xpack.enterpriseSearch.searchExperiences.guide.featuresTitle": "Fonctionnalités", "xpack.enterpriseSearch.searchExperiences.guide.githubLink": "Search UI sur GitHub", "xpack.enterpriseSearch.searchExperiences.guide.pageTitle": "Construire une expérience de recherche avec Search UI", - "xpack.enterpriseSearch.searchExperiences.guide.tutorials.appSearch.description": "Construisez une expérience de recherche avec App Search et Search UI.", "xpack.enterpriseSearch.searchExperiences.guide.tutorials.elasticsearch.description": "Construisez une expérience de recherche avec Elasticsearch et Search UI.", - "xpack.enterpriseSearch.searchExperiences.guide.tutorials.workplaceSearch.description": "Construisez une expérience de recherche avec Workplace Search et Search UI.", "xpack.enterpriseSearch.searchExperiences.guide.tutorialsTitle": "Démarrez rapidement grâce à un tutoriel", "xpack.enterpriseSearch.searchExperiences.navTitle": "Expériences de recherche", "xpack.enterpriseSearch.searchExperiences.productDescription": "Construisez une expérience de recherche attrayante et intuitive sans perdre votre temps à tout réinventer.", @@ -18608,7 +17606,6 @@ "xpack.enterpriseSearch.searchNav.otherTools": "Autres outils", "xpack.enterpriseSearch.searchNav.relevance": "Pertinence", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "Intelligence artificielle de 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", "xpack.enterpriseSearch.selectConnector.connectorClientBadgeLabel": "Autogéré", "xpack.enterpriseSearch.selectConnector.h4.connectorClientsLabel": "Connecteurs autogérés", @@ -18638,11 +17635,7 @@ "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionExistsError": "Le nom de collection existe déjà. Choisissez un autre nom.", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionNotFoundErrorMessage": "Collection d'analyses introuvable", "xpack.enterpriseSearch.server.routes.addConnector.connectorExistsError": "Le connecteur ou l'index existe déjà", - "xpack.enterpriseSearch.server.routes.addCrawler.connectorExistsError": "Un connecteur existe déjà pour cet index", - "xpack.enterpriseSearch.server.routes.addCrawler.crawlerExistsError": "Un robot d'indexation existe déjà pour cet index", - "xpack.enterpriseSearch.server.routes.addCrawler.indexExistsError": "L'index existe déjà.", "xpack.enterpriseSearch.server.routes.checkKibanaLogsMessage": "{errorMessage} Vérifiez les logs du serveur Kibana pour plus de détails.", - "xpack.enterpriseSearch.server.routes.configData.errorMessage": "Erreur lors de la récupération des données à partir d'Enterprise Search", "xpack.enterpriseSearch.server.routes.connectors.expensive_query_not_allowed_error": "Les requêtes de recherche lourdes ne sont pas autorisées. \"recherche.autoriser_recherches_lourdes\" est défini comme faux", "xpack.enterpriseSearch.server.routes.connectors.generateConfiguration.indexAlreadyExistsError": "Impossible de trouver un nom de connecteur unique", "xpack.enterpriseSearch.server.routes.connectors.resource_not_found_error": "Le connecteur avec l'ID {connectorId} est introuvable.", @@ -18653,7 +17646,6 @@ "xpack.enterpriseSearch.server.routes.createSearchApplication.searchApplciationExistsError": "Le nom de l’application de recherche est déjà pris. Choisissez un autre nom.", "xpack.enterpriseSearch.server.routes.createSearchApplication.searchApplicationInvalidName": "Nom de l'application de recherche non valide. {exceptionReason}", "xpack.enterpriseSearch.server.routes.errorLogMessage": "Une erreur s'est produite lors de la résolution de la requête en {requestUrl} : {errorMessage}", - "xpack.enterpriseSearch.server.routes.fetchCrawlerMultipleSchedules.documentNotFoundError": "Les données du robot d'indexation sont introuvables.", "xpack.enterpriseSearch.server.routes.fetchSearchApplicationFieldCapabilities.error": "Impossible de trouver l'application de recherche", "xpack.enterpriseSearch.server.routes.fetchSearchApplicationFieldCapabilities.missingAliasError": "L'alias de l'application de recherche est manquant.", "xpack.enterpriseSearch.server.routes.indices.existsErrorLogMessage": "Une erreur s'est produite lors de la résolution de la requête en {requestUrl}", @@ -18661,10 +17653,8 @@ "xpack.enterpriseSearch.server.routes.indices.pipelines.indexMissingError": "L'index {indexName} n'existe pas", "xpack.enterpriseSearch.server.routes.indices.pipelines.pipelineMissingError": "Le pipeline {pipelineName} n'existe pas", "xpack.enterpriseSearch.server.routes.indices.pipelines.pipelineNotFoundError": "Le pipeline {pipelineName} n'existe pas", - "xpack.enterpriseSearch.server.routes.recreateConnector.connectorExistsError": "Un connecteur existe déjà pour cet index", "xpack.enterpriseSearch.server.routes.unauthorizedError": "Vous ne disposez pas d'autorisations suffisantes.", "xpack.enterpriseSearch.server.routes.uncaughtExceptionError": "Search a rencontré une erreur.", - "xpack.enterpriseSearch.server.routes.updateHtmlExtraction.noCrawlerFound": "Impossible de trouver un robot d'indexation pour cet index", "xpack.enterpriseSearch.server.utils.invalidEnumValue": "Valeur {value} non autorisée pour le champ {fieldName}", "xpack.enterpriseSearch.setupGuide.cloud.step1.instruction1": "Visitez la console Elastic Cloud pour {editDeploymentLink}.", "xpack.enterpriseSearch.setupGuide.cloud.step1.instruction1LinkText": "modifier votre déploiement", @@ -18741,759 +17731,7 @@ "xpack.enterpriseSearch.whatsNextBox.searchPlaygroundButtonLabel": "Rechercher dans Playground", "xpack.enterpriseSearch.whatsNextBox.whatsNextPanelDescription": "Vous pouvez synchroniser manuellement vos données, planifier une synchronisation récurrente ou consulter vos documents.", "xpack.enterpriseSearch.whatsNextBox.whatsNextPanelLabel": "Et ensuite ?", - "xpack.enterpriseSearch.workplaceSearch.accountNav.account.link": "Mon compte", - "xpack.enterpriseSearch.workplaceSearch.accountNav.logout.link": "Se déconnecter", - "xpack.enterpriseSearch.workplaceSearch.accountNav.orgDashboard.link": "Accéder au tableau de bord organisationnel", - "xpack.enterpriseSearch.workplaceSearch.accountNav.search.link": "Rechercher", - "xpack.enterpriseSearch.workplaceSearch.accountNav.settings.link": "Paramètres du compte", - "xpack.enterpriseSearch.workplaceSearch.accountNav.sources.link": "Sources de contenu", - "xpack.enterpriseSearch.workplaceSearch.accountSettings.description": "Gérez les accès, les mots de passe et les autres paramètres du compte.", - "xpack.enterpriseSearch.workplaceSearch.accountSettings.title": "Paramètres du compte", - "xpack.enterpriseSearch.workplaceSearch.activityFeedEmptyDefault.title": "Votre organisation ne comporte aucune activité récente", - "xpack.enterpriseSearch.workplaceSearch.activityFeedNamedDefault.title": "{name} ne comporte aucune activité récente", - "xpack.enterpriseSearch.workplaceSearch.add.label": "Ajouter", - "xpack.enterpriseSearch.workplaceSearch.addField.label": "Ajouter un champ", - "xpack.enterpriseSearch.workplaceSearch.addSource.addSourceHeader.externalConnectorLabel": "Pack de connecteurs déployés en externe", - "xpack.enterpriseSearch.workplaceSearch.and": "et", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteLabel": "Voulez-vous vraiment supprimer cette clé d'API ? Cette action ne peut pas être annulée.", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteTitle": "Supprimer la clé d'API", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copied.tooltip": "Copié", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiEndpoint.buttonLabel": "Copiez le point de terminaison d'API dans le presse-papiers.", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiKey.buttonLabel": "Copier la clé d'API dans le presse-papiers.", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.createdMessage": "La clé d'API \"{name}\" a été créée", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.createKey.buttonLabel": "Créer une clé", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.deleteApiKey.buttonDescription": "Supprimer la clé d'API", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.deletedMessage": "La clé d'API \"{name}\" a été supprimée", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyBody": "Autorisez les applications à accéder à Elastic Workplace Search en votre nom.", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyButtonLabel": "En savoir plus sur les clés d'API", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyTitle": "Créer votre première clé d'API", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.endpointTitle": "Point de terminaison", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.flyoutTitle": "Créer une nouvelle clé", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.formHelpText": "Votre clé sera nommée : {name}", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.formLabel": "Nom de clé", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.hideApiKeyLabel": "Masquer la clé d'API", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.keyTitle": "Clé", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.namePlaceholder": "par exemple, ma-clé-d'api", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.nameTitle": "Nom", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.showApiKeyLabel": "Afficher la clé d'API", - "xpack.enterpriseSearch.workplaceSearch.baseUri.label": "URI de base", - "xpack.enterpriseSearch.workplaceSearch.baseUrl.label": "URL de base", - "xpack.enterpriseSearch.workplaceSearch.betweenLabel": "entre", - "xpack.enterpriseSearch.workplaceSearch.blockLabel": "Bloc", - "xpack.enterpriseSearch.workplaceSearch.clientId.label": "ID client", - "xpack.enterpriseSearch.workplaceSearch.clientSecret.label": "Identifiant client secret", - "xpack.enterpriseSearch.workplaceSearch.comfirmModal.title": "Veuillez confirmer", - "xpack.enterpriseSearch.workplaceSearch.confidential.label": "Confidentiel", - "xpack.enterpriseSearch.workplaceSearch.confidential.text": "Désélectionnez cette option pour les environnements dans lesquels l'identifiant client secret ne peut pas être tenu confidentiel, tels que les applications mobiles natives et les applications à page unique.", - "xpack.enterpriseSearch.workplaceSearch.configure.button": "Configurer", - "xpack.enterpriseSearch.workplaceSearch.confirmChanges.text": "Confirmer les modifications", - "xpack.enterpriseSearch.workplaceSearch.connectors.header.description": "Tous vos connecteurs configurables.", - "xpack.enterpriseSearch.workplaceSearch.connectors.header.title": "Connecteurs de sources de contenu", - "xpack.enterpriseSearch.workplaceSearch.consumerKey.label": "Clé consommateur", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.description": "Pour vous préparer à la configuration, consultez notre {deploymentGuideLink} pour connaître toutes les conditions préalables nécessaires au déploiement rapide du pack de connecteurs. Finalisez votre configuration dans Enterprise Search en définissant l'URL et la clé d'API du connecteur à l'étape suivante.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.heading": "Le {name} est entièrement personnalisable et sera autogéré sur l'infrastructure de votre choix.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.linkLabel": "documentation", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.description": "Créez, modifiez et déployez un pack de connecteurs pour votre cas d'utilisation.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.learnMoreButtonLabel": "En savoir plus", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.registerButtonLabel": "Enregistrer votre déploiement", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.reviewButtonLabel": "Vérifier votre pack de connecteurs", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.title": "Vous ne trouvez pas ce que vous cherchez ?", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.configCompleted.feedbackCallOutText": "Vous avez des commentaires sur le déploiement d'un pack de connecteurs {name} ? Faites-nous en part.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.apiKeyLabel": "Clé d'API du connecteur", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.error": "Veuillez utiliser une URL valide", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.helpText": "Les URL doivent commencer par https://", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.insecureTitle": "Connexion non sécurisée", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.insecureWarning": "Votre connecteur utilise une connexion HTTP, qui n'est pas privée. Les informations synchronisées par ce connecteur peuvent être vues par d'autres personnes. Connectez-vous via HTTPS pour sécuriser vos informations.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.registerButtonLabel": "Enregistrer le déploiement", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.stepTitle": "Fournir les informations de configuration appropriées", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.urlLabel": "URL du connecteur", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.emptyBody": "Les sources seront disponibles pour la recherche lorsqu'un administrateur les ajoutera à cette organisation.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.emptyTitle": "Aucune source disponible", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.newSourceDescription": "Lorsque vous configurez et connectez une source, vous créez des entités distinctes avec un contenu interrogeable synchronisé à partir de la plateforme de contenus. Pour plus de flexibilité, une source peut être ajoutée soit en utilisant un des connecteurs de source disponibles, soit par le biais de sources d'API personnalisées.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.noSourcesTitle": "Configurer et connecter votre première source de contenu", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourceDescription": "Les sources de contenu organisationnelles sont disponibles pour toute votre organisation et peuvent être attribuées à des groupes d'utilisateurs spécifiques.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourcesTitle": "Ajouter une source de contenu organisationnelle", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.placeholder": "Filtrer les sources…", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.privateSourceDescription": "Connectez une nouvelle source pour ajouter son contenu et ses documents à votre expérience de recherche.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.privateSourcesTitle": "Ajouter une nouvelle source de contenu", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.body": "Configurer une source disponible ou créer votre source à l'aide de la", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.connectButtonLabel": "Connecter", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.customSource.button": "Source d'API personnalisée", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.emptyState": "Aucune source disponible ne correspond à votre recherche.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.title": "Disponible pour la configuration", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.toolTipContent": "{name} est configurable en tant que source privée, disponible avec l'abonnement Platinum.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.back.button": "Retour", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.alt.text": "Illustration de la connexion", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.configure.button": "Enregistrer votre déploiement", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.heading": "Étape 1", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.repositoryLinkLabel": "référentiel", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.text": "Dans le pack de connecteurs {repositoryLink}, vous trouverez tout ce dont vous avez besoin pour comprendre le framework des connecteurs et configurer votre environnement de codage.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.title": "Créer ou modifier le code", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.documentationLinkLabel": "documentation", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.heading": "Étape 2", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.text": "Les packs de connecteurs sont autogérés dans l'infrastructure que vous déployez. Vérifiez les prérequis dans la {documentationLink} pour commencer votre déploiement.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.title": "Déployer votre pack de connecteurs personnalisé", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step3.text": "Une fois que vous avez créé et déployé votre pack de connecteurs, revenez sur cette page pour enregistrer votre déploiement de pack de connecteurs, finalisez votre configuration et connectez-vous à vos sources de contenu.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.steps.title": "Créer et déployer un pack de connecteurs personnalisé pour ajouter des données de sources de contenu personnalisées, ou modifier le comportement des sources de contenu internes", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.configureNew.button": "Configurer une nouvelle source de contenu", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.connect.button": "Connecter {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.heading": "{name} configuré", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.orgCanConnect.message": "{name} peut maintenant être connecté à Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.personalConnectLink.message": "Les utilisateurs peuvent maintenant lier leurs comptes {name} à partir de leurs tableaux de bord personnels.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.button": "Découvrez plus d'informations sur les sources de contenu privées.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.link": "activation de la connexion source privée", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.button": "Créer une source d'API personnalisée", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.createNamedSourceButtonLabel": "Configurer {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.description": "Pour vous préparer à la configuration, consultez notre {deploymentGuideLink} pour connaître toutes les conditions préalables nécessaires au déploiement rapide du pack de connecteurs. Finalisez votre configuration dans Enterprise Search en donnant un nom descriptif à la source de contenu {name}, et mettez à jour le fichier de configuration du connecteur avec l'identifiant de la source fourni à l'étape suivante.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.discussLinkLabel": "Des questions ? Discutez-en ici.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.feedbackLinkLabel": "Nous cherchons toujours à nous améliorer. Donnez-nous votre avis", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.githubRepoLinkLabel": "Personnaliser le connecteur ici.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.heading": "Le connecteur {name} est entièrement personnalisable, et sera autogéré sur l'infrastructure de votre choix.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.linkLabel": "documentation", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.description": "{link} pour en savoir plus sur les sources d'API personnalisées.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.text": "Lire la documentation", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.title": "Comment ajouter {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.applicationPortal.button": "Portail de l'application {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.discuss.buttonLabel": "Des questions ? Discutez-en ici.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.button": "Instructions", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.description": "Configurez un connecteur personnalisé pour une plus grande flexibilité de configuration et un meilleur contrôle.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.title": "Connecteur personnalisé", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.betaLabel": "Version d'évaluation technique", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.button": "Instructions", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.description": "Déployez ce pack de connecteurs sur une infrastructure autogérée pour des cas d'utilisation avancés.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.recommendedLabel": "Recommandé", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.reviewButtonLabel": "Vérifier le pack de connecteurs", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.title": "Pack de connecteurs", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.button": "Connecter", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.description": "Utilisez ce connecteur pour démarrer rapidement sans déployer d'infrastructure supplémentaire.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.title": "Connecteur", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.alt.text": "Illustration de la connexion", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button": "Configurer {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.badge": "Action ponctuelle", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.heading": "Étape 1", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.text": "Configurez une application OAuth sécurisée via la source de contenu que vous utiliserez, ou que votre équipe utilisera, pour connecter et synchroniser du contenu. Vous n'aurez besoin d'effectuer cette configuration qu'une seule fois par source de contenu.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.title": "Configurer un {badge} d'application OAuth", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.heading": "Étape 2", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.text": "Utilisez la nouvelle application OAuth pour connecter n'importe quel nombre d'instances de la source de contenu à Workplace Search.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.title": "Connecter la source de contenu", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.text": "Configuration rapide, après laquelle tous vos documents deviendront interrogeables.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.title": "Comment ajouter {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.button": "Terminer la connexion", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.label": "Sélectionner les organisations GitHub à synchroniser", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.accountOnlyTooltip": "Source de contenu privée. Chaque utilisateur doit ajouter la source de contenu à partir de son propre tableau de bord personnel.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.body": "Configuré et prêt pour la connexion.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.connectAnotherButton": "Connecter un autre", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.connectButton": "Connecter", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.emptyState": "Aucune source configurée ne correspond à votre recherche.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.title": "Sources de contenu configurées", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.unConnectedTooltip": "Aucune source connectée", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSourcesList.betaBadge": "Bêta", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.button": "Connecter {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.docPermissionsUnavailable.message": "Les autorisations de niveau document ne sont pas encore disponibles pour cette source. {link}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.needsPermissions.text": "Les informations d'autorisation de niveau document seront synchronisées. Une configuration supplémentaire est requise après la connexion initiale pour que les documents soient disponibles pour la recherche.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.notSynced.text": "Tous les documents accessibles à l'utilisateur du service qui se connecte seront synchronisés et deviendront disponibles pour les utilisateurs de l'organisation ou du groupe. Les documents sont immédiatement disponibles pour la recherche.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.notSynced.title": "Les autorisations de niveau document ne seront pas synchronisées", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.permissions.label": "Activer la synchronisation des autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.permissions.title": "Les autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.whichOption.link": "Quelle option choisir ?", - "xpack.enterpriseSearch.workplaceSearch.contentSource.formSourceAddedSuccessMessage": "{name} connecté", - "xpack.enterpriseSearch.workplaceSearch.contentSource.includedFeaturesTitle": "Fonctionnalités incluses", - "xpack.enterpriseSearch.workplaceSearch.contentSource.reAuthenticate.body": "Vos informations d'identification {name} ne sont plus valides. Veuillez vous authentifier à nouveau avec les informations d'identification d'origine pour reprendre la synchronisation du contenu.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.reAuthenticate.button": "Authentifier à nouveau {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.baseUrlLabel": "URL de base", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.button": "Enregistrer la configuration", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.clientIDLabel": "ID client", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.clientSecretLabel": "Identifiant client secret", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.oauthStep1": "Créer une application OAuth dans le compte {sourceName} de votre organisation", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.oauthStep2": "Fournir les informations de configuration appropriées", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.body1": "Vos points de terminaison sont prêts à accepter les requêtes.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.configureNewSourceButtonLabel": "Configurer une nouvelle source de contenu", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.heading": "{name} créé", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.addField.button": "Ajouter un champ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.description": "Un schéma est créé à votre place une fois que vous avez indexé quelques documents. Cliquez ci-dessous pour créer des champs de schéma à l'avance.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.title": "La source de contenu ne possède pas de schéma", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.dataType": "Type de données", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.fieldName": "Nom du champ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.heading": "Erreurs de modification du schéma", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.message": "Oups, nous n'avons pas trouvé d'erreur pour ce schéma.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.fieldAdded.message": "Nouveau champ ajouté.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.noResults.message": "Aucun résultat pour \"{filterValue}\".", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.placeholder": "Filtrer les champs de schéma…", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.description": "Ajouter de nouveaux champs ou modifier les types des champs existants", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.title": "Gérer le schéma de la source", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.newFieldExists.message": "Le nouveau champ existe déjà : {fieldName}.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.save.button": "Enregistrer le schéma", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updated.message": "Schéma mis à jour.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.text": "Les autorisations de niveau document gèrent l'accès au contenu utilisateur en fonction des règles définies. Autorisez ou refusez l'accès à certains documents pour des personnes et des groupes.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.title": "Autorisations de niveau document disponibles avec la licence Platinum", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.description": "Tous les documents accessibles à l'utilisateur du service qui se connecte seront synchronisés et deviendront disponibles pour les utilisateurs de l'organisation ou du groupe. Les documents sont immédiatement disponibles pour la recherche", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.title": "Permissions d'accès globales", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.description": "Les résultats renvoyés vous sont spécifiques et pertinents. La connexion de cette source n'expose pas vos données personnelles aux autres utilisateurs de la recherche. Vous êtes la seule personne à y avoir accès.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.title": "Toujours privé", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.description": "Les données des messages et autres informations sont interrogeables en temps réel à partir de l'expérience Workplace Search.", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.title": "Toujours à jour", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.description": "Les éléments suivants sont interrogeables :", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.title": "Contenu interrogeable", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncedItems.title": "Éléments synchronisés", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.text": "Cette source récupère le nouveau contenu de {name} toutes les {duration} (suivant la synchronisation initiale).", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.time": "2 heures", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.title": "Synchronisations toutes les 2 heures", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.color.label": "Couleur", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.createdBy.label": "Créé par", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.description": "Personnalisez le contenu et l'apparence des résultats de recherche de votre source d'API personnalisée.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.title": "Paramètres d'affichage", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.body": "Vous devez posséder un contenu à afficher pour pouvoir configurer les paramètres d'affichage.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.title": "Vous n'avez pas encore de contenu", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.emptyFields.description": "Ajoutez des champs et déplacez-les pour obtenir l'ordre d'affichage souhaité.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.description": "Un document correspondant apparaîtra sous forme de fiche unique mise en évidence.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.title": "Résultats proposés", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.go.button": "Go", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.lastUpdated.heading": "Dernière mise à jour", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.mediaType.label": "Type de support", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.optionalArea.text": "Cette zone est facultative", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.preview.title": "Aperçu", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.reset.button": "Réinitialiser", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.resultDetail.label": "Détails du résultat", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.label": "Résultats de recherche", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.title": "Paramètres des résultats de recherche", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResultsRow.helpText": "Cette zone est facultative", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.description": "Les documents à correspondance approchante apparaîtront sous forme d'ensemble.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.title": "Résultats standard", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.subtitle.label": "Sous-titre", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.success.message": "Les paramètres d'affichage ont bien été mis à jour.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.heading": "Titre", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.label": "Titre", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.type.label": "Type", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.unsaved.message": "Vos paramètres d'affichage n'ont pas été enregistrés. Voulez-vous vraiment quitter ?", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.updatedBy.label": "Mis à jour par", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.url.label": "URL", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.visibleFields.title": "Champs visibles", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.estimateSummaryLabel": "La durée estimée de l'opération est de {estimateDisplay}.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.frequencyItemLabel": "Effectuer un {label} à chaque", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.lastRunLabel": "dernière exécution", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.lastRunSummary": "Cette synchro {lastRunStrong} {lastRunTime}.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartLabel": "Prochaine exécution", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartSummary": "{nextStartStrong} commencera {nextStartTime}.", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementContentExtractionLabel": "Synchroniser le texte intégral à partir de fichiers", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementGlobalConfigLabel": "Synchroniser les miniatures – désactivé au niveau de la configuration globale", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementSynchronizeLabel": "Synchroniser cette source", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementThumbnailsLabel": "Synchroniser les miniatures", - "xpack.enterpriseSearch.workplaceSearch.copied.tooltip": "Copié !", - "xpack.enterpriseSearch.workplaceSearch.copy.tooltip": "Copier dans le presse-papiers", - "xpack.enterpriseSearch.workplaceSearch.copyText": "Copier", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.copied.tooltip": "Copié !", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.copy.tooltip": "Copier dans le presse-papiers", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.show.tooltip": "Afficher {credential}.", - "xpack.enterpriseSearch.workplaceSearch.credentials.description": "Utilisez les informations d'identification suivantes dans votre client pour demander des tokens d'accès à notre serveur d'authentification.", - "xpack.enterpriseSearch.workplaceSearch.credentials.title": "Informations d'identification", - "xpack.enterpriseSearch.workplaceSearch.cta": "Explorer", - "xpack.enterpriseSearch.workplaceSearch.customize.header.description": "Personnalisez les paramètres généraux d'organisation.", - "xpack.enterpriseSearch.workplaceSearch.customize.header.title": "Personnaliser Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.customize.name.button": "Enregistrer le nom de l'organisation", - "xpack.enterpriseSearch.workplaceSearch.customize.name.label": "Nom de l'organisation", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.documentationLinkLabel": "Documentation du connecteur {name}", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.genericDocumentationHelpText": "Consultez la {documentationLink} pour apprendre comment construire et déployer votre propre connecteur sur l'infrastructure autogérée de votre choix.", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.genericDocumentationLabel": "Documentation source de l'API personnalisée", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.preconfiguredDocumentationHelpText": "Examinez la {documentationLink} et déployez le pack de connecteurs pour qu'il soit autogéré sur l'infrastructure de votre choix.", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.preconfiguredRepositoryInstructions": "Configurez votre connecteur en clonant le {githubRepositoryLink}.", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.repositoryLinkLabel": "Référentiel du connecteur {name}", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.sourceIdentifierHelpText": "Spécifiez l'identificateur de source suivant ainsi qu'un {apiKeyLink} dans le fichier de configuration du connecteur déployé pour synchroniser les documents.", - "xpack.enterpriseSearch.workplaceSearch.deployment.title": "Déploiement", - "xpack.enterpriseSearch.workplaceSearch.description.label": "Description", - "xpack.enterpriseSearch.workplaceSearch.documentsHeader": "Documents", - "xpack.enterpriseSearch.workplaceSearch.editField.label": "Modifier le champ", "xpack.enterpriseSearch.workplaceSearch.explorePlatinumFeatures.link": "Explorer les fonctionnalités Platinum", - "xpack.enterpriseSearch.workplaceSearch.externalConnectorApiKey.label": "Clé d'API du connecteur", - "xpack.enterpriseSearch.workplaceSearch.externalConnectorUrl.label": "URL du connecteur", - "xpack.enterpriseSearch.workplaceSearch.field.label": "Champ", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.contact": "vous contacter", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.description": "En envoyant vos commentaires, vous reconnaissez que vous avez lu et que vous acceptez nos {termsOfService}, et vous autorisez Elastic à vous {contact} concernant des produits et services similaires en utilisant les informations que vous avez fournies plus haut. Pour en savoir plus ou pour vous désinscrire à tout moment, consultez {privacyStatementLink}.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.Label": "Souhaitez-vous nous faire part d'autres commentaires ?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.optionalLabel": "Facultatif", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readDataPrivacyStatementLink": "Déclaration de confidentialité d'Elastic", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readTermsOfService": "Conditions générales d'utilisation", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.action.Label": "Ajouter des analyses de recherche", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureDescription": "Saviez-vous qu'il est facile d'analyser le comportement de recherche et de clic de vos utilisateurs avec Behavioral Analytics ? Instrumentez votre site web ou votre application pour suivre les actions pertinentes des utilisateurs.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureName": "Utiliser Behavioral Analytics", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureButtonLabel": "Utiliser un connecteur", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureDescription": "Saviez-vous que vous avez accès à une puissante extraction de contenu grâce aux connecteurs Elastic ? Utilisez nos puissantes capacités d'extraction largement adaptables pour extraire des contenus de vos fichiers.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureName": "Utiliser les connecteurs Elastic", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureButtonLabel": "Utiliser un connecteur", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureDescription": "Saviez-vous que les connecteurs Elastic sont à présent disponibles ? Vous pouvez maintenir la synchronisation du contenu de vos sources de données avec vos index optimisés pour la recherche !", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureName": "Utiliser les connecteurs Elastic", - "xpack.enterpriseSearch.workplaceSearch.gateForm.description": "Le produit autonome Workplace Search reste disponible en mode maintenance et n'est pas recommandé pour de nouvelles expériences de recherche. Nous vous recommandons plutôt d'utiliser notre ensemble d'outils natifs Elasticsearch, que notre équipe développe et améliore de manière active, pour votre cas d'utilisation Workplace Search. Ces outils offrent la flexibilité et la possibilité d'interagir directement avec les index Elasticsearch. Pour en savoir plus sur le contexte de ce recentrage, consultez {blogUrl}. Pour vous aider à choisir l'outil le mieux adapté à votre cas d'utilisation, nous avons créé cet assistant de recommandation. Indiquez-nous les fonctionnalités dont vous avez besoin et nous vous conseillerons les solutions les mieux adaptées. Si vous souhaitez toujours utiliser le produit autonome Workplace Search à ce stade, vous pourrez le faire après avoir envoyé le formulaire.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.docLevelPermissions.featureDescription": "Saviez-vous que vous pouvez restreindre l'accès à des documents dans vos index Elasticsearch en fonction des autorisations des utilisateurs et des groupes ? Renvoie uniquement des résultats de recherche autorisés pour les utilisateurs dotés d'une sécurité au niveau du document d'Elastic.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.docLevelPermissions.featureName": "Utiliser les connecteurs Elastic", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.learnMore": "En savoir plus", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.subTitle": "En fonction de votre sélection, nous vous recommandons :", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.title": "Équivalent natif d'Elasticsearch", - "xpack.enterpriseSearch.workplaceSearch.gateForm.featureOther.Label": "Pouvez-vous expliquer quelles autres fonctionnalités vous recherchez ?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.features.Label": "Quelle fonctionnalité de Workplace Search envisagez-vous d'utiliser ?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.features.selectOption": "Sélectionner une option", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label": "Voulez-vous rejoindre nos travaux de recherche en tant qu'utilisateur pour améliorer Elasticsearch ?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label.No": "Non", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label.Yes": "Oui", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.optionalLabel": "Facultatif", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.action.Label": "Construire une expérience de recherche avec Search UI", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.addOn.learnMoreLabel": "Search UI", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureDescription": "Des applications basées sur la recherche qui exploitent toute la puissance d'Elasticsearch ! Créez une recherche unifiée à l'aide d'applications de recherche ou intégrez-la directement à votre interface utilisateur existante avec Search UI.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureName": "Créer une application de recherche", - "xpack.enterpriseSearch.workplaceSearch.gateForm.submit": "Envoyer", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.description": "Enregistrer et réviser les interactions des utilisateurs avec les résultat de recherche", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.inputDisplay": "Analyse", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.title": "Analyse", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.description": "Extraire le contenu des fichiers source synchronisés pour les rendre interrogeables", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.inputDisplay": "Extraction du contenu", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.title": "Extraction du contenu", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.description": "Extraire le contenu des fichiers source synchronisés pour les rendre interrogeables", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.inputDisplay": "Sources de contenu", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.title": "Sources de contenu", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.description": "Contrôler l'accès à des documents spécifiques", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.inputDisplay": "Les autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.title": "Les autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.description": "Une autre fonctionnalité qui n'est pas énumérée ici", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.inputDisplay": "Autre", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.title": "Autre", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.description": "Applications optimisées pour la recherche facile à intégrer", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.inputDisplay": "Une recherche immédiatement exploitable", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.title": "Une recherche immédiatement exploitable", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.description": "Lier différents mots ou différentes phrases dont les significations sont équivalentes", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.inputDisplay": "Synonymes", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.title": "Synonymes", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.action.Label": "Rechercher à l'aide de synonymes", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.featureDescription": "Saviez-vous que vous pouvez améliorer votre expérience de recherche en cherchant à l'aide de synonymes ? Utilisez notre API Synonyms pour créer et gérer facilement les ensembles de synonymes.", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.featureName": "Rechercher avec l'API Synonyms", - "xpack.enterpriseSearch.workplaceSearch.gateForm.title": "Avant de commencer...", - "xpack.enterpriseSearch.workplaceSearch.gateForm.viewBlog": "blog", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.heading": "Ajouter un groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.label": "Nom du groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.submit.action": "Ajouter le groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroupForm.action": "Créer un groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.clearFilters.action": "Effacer les filtres", - "xpack.enterpriseSearch.workplaceSearch.groups.contentSourceCountHeading": "{numSources} sources de contenu organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.groups.description": "Attribuez des sources de contenu organisationnelles et des utilisateurs à des groupes pour créer des expériences de recherche pertinentes pour différentes équipes internes.", - "xpack.enterpriseSearch.workplaceSearch.groups.filterGroups.placeholder": "Filtrer les groupes par nom…", - "xpack.enterpriseSearch.workplaceSearch.groups.filterSources.buttonText": "Sources", - "xpack.enterpriseSearch.workplaceSearch.groups.groupDeleted": "Le groupe \"{groupName}\" a bien été supprimé.", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "Gérer {label}", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "Il semblerait que vous n'ayez pas encore ajouté de source de contenu organisationnelle.", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.title": "Oups !", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.deselectButton.text": "Tout désélectionner", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.selectButton.text": "Tout sélectionner", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton": "Ajouter une source organisationnelle", - "xpack.enterpriseSearch.workplaceSearch.groups.groupNotFound": "Impossible de trouver le groupe ayant l'ID : \"{groupId}\".", - "xpack.enterpriseSearch.workplaceSearch.groups.groupPrioritizationUpdated": "La hiérarchisation des sources organisationnelles a bien été mise à jour.", - "xpack.enterpriseSearch.workplaceSearch.groups.groupRenamed": "Le nom du groupe a bien été changé en \"{groupName}\".", - "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "Les sources de contenu organisationnelles ont bien été mises à jour.", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupPagination.label": "Groupes", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "Regrouper", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "Sources de contenu", - "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "Dernière mise à jour {updatedAt}.", - "xpack.enterpriseSearch.workplaceSearch.groups.heading": "Gérer les groupes", - "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "Inviter des utilisateurs", - "xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action": "Gérer le groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.newGroupSavedSuccess": "{groupName} a bien été créé", - "xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage": "Aucune source de contenu organisationnelle", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveButtonText": "Supprimer {name}", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "Votre groupe sera supprimé de Workplace Search. Voulez-vous vraiment supprimer {name} ?", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "Confirmer", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "Aucune source de contenu n'est partagée avec ce groupe.", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription": "Interrogeable par tous les utilisateurs du groupe \"{name}\".", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "Sources de contenu du groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "Les utilisateurs attribués à ce groupe obtiennent un accès aux données et au contenu des sources définis ci-dessus. Les attributions des utilisateurs pour ce groupe peuvent être gérées dans la zone Utilisateurs et rôles.", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.manageSourcesButtonText": "Gérer les sources de contenu organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.manageUsersButtonText": "Gérer les utilisateurs et les rôles", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.nameSectionDescription": "Personnalisez le nom de ce groupe.", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.nameSectionTitle": "Nom du groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeButtonText": "Retirer le groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeSectionDescription": "Cette action ne peut pas être annulée.", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeSectionTitle": "Retirer ce groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.saveNameButtonText": "Enregistrer le nom", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.usersSectionTitle": "Utilisateurs du groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.searchResults.notFoound": "Aucun résultat trouvé.", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerDescription": "Calibrez l'importance relative des documents pour les sources de contenu du groupe.", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerTitle": "Hiérarchisation des sources de contenu organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.priorityTableHeader": "Priorité de pertinence", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.sourceTableHeader": "Source", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateBody": "Partagez deux sources ou plus avec {groupName} pour personnaliser la hiérarchisation des sources.", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateButtonText": "Ajouter des sources de contenu organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateTitle": "Aucune source n'est partagée avec ce groupe", - "xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalLabel": "sources de contenu organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalTitle": "Sélectionner des sources de contenu à partager avec {groupName}", - "xpack.enterpriseSearch.workplaceSearch.keepEditing.button": "Continuer la modification", - "xpack.enterpriseSearch.workplaceSearch.label.label": "Étiquette", - "xpack.enterpriseSearch.workplaceSearch.name.label": "Nom", - "xpack.enterpriseSearch.workplaceSearch.nav.addSource": "Ajouter une source", - "xpack.enterpriseSearch.workplaceSearch.nav.apiKeys": "Clés d'API", - "xpack.enterpriseSearch.workplaceSearch.nav.content": "Contenu", - "xpack.enterpriseSearch.workplaceSearch.nav.displaySettings": "Paramètres d'affichage", - "xpack.enterpriseSearch.workplaceSearch.nav.groups": "Groupes", - "xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview": "Aperçu", - "xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization": "Hiérarchisation des sources", - "xpack.enterpriseSearch.workplaceSearch.nav.overview": "Aperçu", - "xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard": "Afficher mon tableau de bord personnel", - "xpack.enterpriseSearch.workplaceSearch.nav.roleMappings": "Utilisateurs et rôles", - "xpack.enterpriseSearch.workplaceSearch.nav.schema": "Schéma", - "xpack.enterpriseSearch.workplaceSearch.nav.searchApplication": "Accéder à l'application de recherche", - "xpack.enterpriseSearch.workplaceSearch.nav.security": "Sécurité", - "xpack.enterpriseSearch.workplaceSearch.nav.settings": "Paramètres", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsCustomize": "Personnaliser", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsOauth": "Application OAuth", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsSourcePrioritization": "Connecteurs de sources de contenu", - "xpack.enterpriseSearch.workplaceSearch.nav.sources": "Sources", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronization": "Synchronisation", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationAssetsAndObjects": "Actifs et objets", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationFrequency": "Fréquence", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "Configurez une application OAuth pour utiliser l'API de recherche de Workplace Search de façon sécurisée. Effectuez une mise à niveau vers la licence Platinum pour activer l'API de recherche et créer votre application OAuth.", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "Configuration de OAuth pour les applications de recherche personnalisées", - "xpack.enterpriseSearch.workplaceSearch.oauth.description": "Créez un client OAuth pour votre organisation.", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationDescription": "Autoriser {strongClientName} à utiliser votre compte ?", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationTitle": "Autorisation requise", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizeButtonLabel": "Autoriser", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.denyButtonLabel": "Refuser", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.httpRedirectWarningMessage": "Cette application utilise un URI de redirection non sécurisé (http)", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.scopesLeadInMessage": "Cette application sera capable de", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.searchScopeDescription": "Interroger vos données", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.unknownScopeDescription": "{unknownAction} vos données", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.writeScopeDescription": "Modifier vos données", - "xpack.enterpriseSearch.workplaceSearch.oauthPersisted.description": "Accédez aux informations d'identification du client OAuth de votre organisation et gérez les paramètres OAuth.", - "xpack.enterpriseSearch.workplaceSearch.ok.button": "Ok", - "xpack.enterpriseSearch.workplaceSearch.onLabel": "le", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "Utilisateurs actifs", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "Invitations", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.organizationalSources": "Sources organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "Sources privées", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "Statistiques d'utilisation", - "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "Nommer votre organisation", - "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "Avant d'inviter vos collègues, nommez votre organisation afin d'améliorer la reconnaissance.", - "xpack.enterpriseSearch.workplaceSearch.overviewHeader.description": "Statistiques et activité de votre organisation", - "xpack.enterpriseSearch.workplaceSearch.overviewHeader.title": "Aperçu de votre organisation", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.description": "Configurez votre organisation en procédant comme suit.", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.title": "Premiers pas avec Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.description": "Ajoutez des sources organisationnelles pour votre organisation afin de commencer les recherches.", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.title": "Sources organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.description": "Invitez vos collègues dans cette organisation pour effectuer des recherches avec vous.", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteFirstUsers.button": "Inviter des utilisateurs", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteMoreUsers.button": "Inviter d'autres utilisateurs", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.title": "Utilisateurs et invitations", - "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "C'est bien, vous avez invité des collègues à effectuer des recherches avec vous.", - "xpack.enterpriseSearch.workplaceSearch.personalDashboard.readOnlyMode.warning": "Workplace Search est actuellement disponible uniquement pour la recherche, en raison de la maintenance périodique. Pour en savoir plus, contactez votre administrateur système.", - "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "Impossible de connecter la source, demandez de l'aide à votre administrateur. Message d'erreur : {error}", - "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "Fonctionnalité Platinum", - "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "Les sources privées requièrent une licence Platinum.", - "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "Source privée", - "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "Sources privées", - "xpack.enterpriseSearch.workplaceSearch.productCardDescription": "Taillé sur mesure pour les équipes professionnelles internes, Workplace Search permet une connectivité instantanée avec les outils de productivité populaires et les sources tierces sur une unique plateforme unifiée.", - "xpack.enterpriseSearch.workplaceSearch.productCta": "Ouvrir Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.productDescription": "Effectuez des recherches dans tous les documents, fichiers et sources disponibles sur votre lieu de travail virtuel.", - "xpack.enterpriseSearch.workplaceSearch.productName": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.publicKey.label": "Clé publique", - "xpack.enterpriseSearch.workplaceSearch.recentActivity.title": "Activité récente", - "xpack.enterpriseSearch.workplaceSearch.recentActivitySourceLink.linkLabel": "Afficher la source", - "xpack.enterpriseSearch.workplaceSearch.redirectHelp.text": "Fournissez un URI par ligne.", - "xpack.enterpriseSearch.workplaceSearch.redirectInsecureError.text": "L'utilisation d'un URI de redirection non sécurisé (http) n'est pas recommandée.", - "xpack.enterpriseSearch.workplaceSearch.redirectNativeHelp.text": "Pour les URI de développement local, utiliser le format", - "xpack.enterpriseSearch.workplaceSearch.redirectSecureError.text": "Ne peut pas contenir d'URI de redirection en double.", - "xpack.enterpriseSearch.workplaceSearch.redirectURIs.label": "URI de redirection", - "xpack.enterpriseSearch.workplaceSearch.remove.button": "Retirer", - "xpack.enterpriseSearch.workplaceSearch.removeField.label": "Retirer le champ", - "xpack.enterpriseSearch.workplaceSearch.reset.button": "Réinitialiser", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.adminRoleTypeDescription": "Les administrateurs disposent d'un accès total à tous les paramètres concernant toute l'organisation, y compris la fonctionnalité de gestion des utilisateurs, des groupes et des sources de contenu.", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.allGroupsDescription": "L'attribution à tous les groupes comprend tous les groupes actuels et futurs tels qu'ils ont été créés ou seront créés et administrés à une date ultérieure.", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.allGroupsLabel": "Attribuer à tous les groupes", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.defaultGroupName": "Par défaut", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentInvalidError": "Il est requis d'attribuer au moins un groupe.", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentLabel": "Attribution de groupe", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsTableHeader": "Accès du groupe", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.specificGroupsDescription": "Effectuez des attributions à un ensemble sélectionné de groupes de manière statique.", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.specificGroupsLabel": "Attribuer à des groupes spécifiques", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.userRoleTypeDescription": "Les fonctionnalités accessibles aux utilisateurs sont limitées à la gestion des interfaces de recherche et des paramètres personnels.", - "xpack.enterpriseSearch.workplaceSearch.roleMappingCreatedMessage": "Création réussie du mapping de rôles.", - "xpack.enterpriseSearch.workplaceSearch.roleMappingDeletedMessage": "Suppression réussie du mapping de rôles", - "xpack.enterpriseSearch.workplaceSearch.roleMappingUpdatedMessage": "Mise à jour réussie du mapping de rôles.", - "xpack.enterpriseSearch.workplaceSearch.saveChanges.button": "Enregistrer les modifications", - "xpack.enterpriseSearch.workplaceSearch.saveSettings.button": "Enregistrer les paramètres", - "xpack.enterpriseSearch.workplaceSearch.searchableHeader": "Interrogeable", - "xpack.enterpriseSearch.workplaceSearch.security.privateSources.description": "Les sources privées sont connectées par des utilisateurs de votre organisation pour créer une expérience de recherche personnalisée.", - "xpack.enterpriseSearch.workplaceSearch.security.privateSourcesToggle.description": "Activer les sources privées pour votre organisation", - "xpack.enterpriseSearch.workplaceSearch.security.privateSourcesUpdateConfirmation.text": "Les mises à jour de la configuration des sources privées prendront effet immédiatement.", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.description": "Une fois configurées, les sources privées distantes sont {enabledStrong} et les utilisateurs peuvent immédiatement connecter la source à partir de leur tableau de bord personnel.", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.enabledStrong": "activées par défaut", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.title": "Aucune source privée distante configurée pour le moment", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesTable.description": "Les sources distantes synchronisent et stockent une quantité limitée de données sur le disque, avec un impact faible sur les ressources de stockage.", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesToggle.text": "Activer les sources privées distantes", - "xpack.enterpriseSearch.workplaceSearch.security.sourceRestrictionsSuccess.message": "Les restrictions des sources ont bien été mises à jour.", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.description": "Une fois configurées, les sources privées standard sont {notEnabledStrong} et doivent être activées pour que les utilisateurs soient autorisés à connecter la source à partir de leur tableau de bord personnel.", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.notEnabledStrong": "non activées par défaut", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.title": "Aucune source privée standard configurée pour le moment", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesTable.description": "Les sources standard synchronisent et stockent toutes les données interrogeables sur le disque, avec un impact directement corrélé sur les ressources de stockage.", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesToggle.text": "Activer les sources privées standard", - "xpack.enterpriseSearch.workplaceSearch.security.unsavedChanges.message": "Vos paramètres de sources privées n'ont pas été enregistrés. Voulez-vous vraiment quitter ?", - "xpack.enterpriseSearch.workplaceSearch.settings.brandText": "Marque", - "xpack.enterpriseSearch.workplaceSearch.settings.configRemoved.message": "Retrait réussi de la configuration de {name}.", - "xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfig.message": "Voulez-vous vraiment retirer la configuration pour {name} ?", - "xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfigTitle": "Retirer la configuration", - "xpack.enterpriseSearch.workplaceSearch.settings.feedbackCallOutText": "Vous avez des commentaires sur le déploiement d'un pack de connecteurs {name} ? Faites-nous en part.", - "xpack.enterpriseSearch.workplaceSearch.settings.iconDescription": "Utilisé comme élément de marque pour les écrans de plus petite taille et les icônes de navigation", - "xpack.enterpriseSearch.workplaceSearch.settings.iconHelpText": "La taille maximale de fichier est de 2 Mo et les proportions recommandées sont de 1:1. Seuls les fichiers PNG sont pris en charge.", - "xpack.enterpriseSearch.workplaceSearch.settings.iconText": "icône", - "xpack.enterpriseSearch.workplaceSearch.settings.logoDescription": "Utilisé comme élément visuel principal de marque dans les applications de recherche prédéfinies", - "xpack.enterpriseSearch.workplaceSearch.settings.logoHelpText": "La taille maximale de fichier est de 2 Mo. Seuls les fichiers PNG sont pris en charge.", - "xpack.enterpriseSearch.workplaceSearch.settings.logoText": "logo", - "xpack.enterpriseSearch.workplaceSearch.settings.oauthAppUpdated.message": "Mise à jour réussie de l'application.", - "xpack.enterpriseSearch.workplaceSearch.settings.organizationLabel": "Organisation", - "xpack.enterpriseSearch.workplaceSearch.settings.orgUpdated.message": "Mise à jour réussie de l'organisation.", - "xpack.enterpriseSearch.workplaceSearch.settings.resetIconDescription": "Vous êtes sur le point de réinitialiser l'icône sur la marque par défaut de Workplace Search.", - "xpack.enterpriseSearch.workplaceSearch.settings.resetImageConfirmationText": "Voulez-vous vraiment continuer ?", - "xpack.enterpriseSearch.workplaceSearch.settings.resetImageTitle": "Réinitialiser sur la marque par défaut", - "xpack.enterpriseSearch.workplaceSearch.settings.resetLogoDescription": "Vous êtes sur le point de réinitialiser le logo sur la marque par défaut de Workplace Search.", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.button": "Premiers pas avec Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.description": "Unifiez vos plateformes de contenu, telles que Google Drive et Salesforce, pour proposer une expérience de recherche personnalisée.", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.imageAlt": "Premiers pas avec Workplace Search : un guide pour vous aider à configurer Workplace Search et vous expliquer son fonctionnement", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.notConfigured": "Workplace Search n'est pas configuré dans Kibana. Suivez les instructions indiquées sur cette page.", - "xpack.enterpriseSearch.workplaceSearch.source.text": "Source", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.baseUrlLabel": "URL de base", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.clientIDLabel": "ID client", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.clientSecretLabel": "Identifiant client secret", - "xpack.enterpriseSearch.workplaceSearch.sourceIdentifier.sourceIdentifierFieldLabel": "Identificateur de source", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.detailsLabel": "Détails", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.reauthenticateStatusLinkLabel": "Authentifier à nouveau", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteLabel": "Distant", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteTooltip": "Les sources distantes s'appuient directement sur le service de recherche de la source ; aucun contenu n'est indexé avec Workplace Search. La vitesse et l'intégrité des résultats dépendent de la santé et des performances du service tiers.", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.searchableToggleLabel": "Bascule pour source interrogeable", - "xpack.enterpriseSearch.workplaceSearch.sources.additionalConfig.heading": "Requiert une configuration supplémentaire", - "xpack.enterpriseSearch.workplaceSearch.sources.apiKeyLabel": "Clé d'API", - "xpack.enterpriseSearch.workplaceSearch.sources.apiKeysTitle": "Clés d'API", - "xpack.enterpriseSearch.workplaceSearch.sources.applicationLinkTitles.github": "Portail du développeur GitHub", - "xpack.enterpriseSearch.workplaceSearch.sources.baseUrlTitles.github": "URL de GitHub Enterprise", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedEmptyStateDescription": "Ajouter une fenêtre de temps bloqué pour n'effectuer les synchronisations qu'au bon moment.", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedEmptyStateTitle": "Vous n'avez pas de fenêtres de temps bloquées", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedWindowsTitle": "Fenêtres de temps bloquées", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.accountManagement": "Gestion des comptes", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.atlassian": "Atlassian", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.bugTracking": "Suivi des bugs", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.chat": "Chat", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.cloud": "Cloud", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.codeRepository": "Référentiel de code", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.collaboration": "Collaboration", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.communication": "Communication", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.crm": "CRM", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.customerRelationshipManagement": "Gestion de la relation client", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.customerService": "Service client", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.email": "E-mail", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.fileSharing": "Partage de fichiers", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.google": "Google", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.gsuite": "GSuite", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.help": "Aide", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.helpdesk": "Assistance", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.instantMessaging": "Messagerie instantanée", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.intranet": "Intranet", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.microsoft": "Microsoft", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.office": "Office 365", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.productivity": "Productivité", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.projectManagement": "Gestion de projets", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.software": "Logiciels", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.storage": "Stockage", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.ticketing": "Suivi des incidents", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.versionControl": "Contrôle des versions", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.wiki": "Wiki", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.workflow": "Workflow", - "xpack.enterpriseSearch.workplaceSearch.sources.config.link": "Modifier les paramètres du connecteur", - "xpack.enterpriseSearch.workplaceSearch.sources.config.title": "Configuration de la source de contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.configuration.title": "Configuration", - "xpack.enterpriseSearch.workplaceSearch.sources.contentLoading.text": "Chargement du contenu…", - "xpack.enterpriseSearch.workplaceSearch.sources.contentSummary.title": "Résumé du contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.contentSummaryLoading.text": "Chargement des détails du résumé...", - "xpack.enterpriseSearch.workplaceSearch.sources.contentType.header": "Type de contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.created.label": "Créé le :", - "xpack.enterpriseSearch.workplaceSearch.sources.customCallout.title": "Vous découvrez les sources personnalisées ?", - "xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.link": "documentation", - "xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.text": "En savoir plus sur l'ajout de contenu dans notre {documentationLink}", - "xpack.enterpriseSearch.workplaceSearch.sources.deletionSyncDescription": "Supprime les documents qui n'existent plus dans la source de contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.deletionSyncLabel": "Synchronisation des suppressions", - "xpack.enterpriseSearch.workplaceSearch.sources.docPermissions.description": "Les autorisations de niveau document gèrent l'accès au contenu selon les attributs individuels ou de groupe. Autorisez ou refusez l'accès à des documents spécifiques.", - "xpack.enterpriseSearch.workplaceSearch.sources.documentation": "Documentation", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.text": "Utiliser des autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.title": "Les autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsDisabled.text": "Désactivées pour cette source", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsLink": "En savoir plus sur la configuration des autorisations de niveau document", - "xpack.enterpriseSearch.workplaceSearch.sources.downloadDiagnosticButton": "Télécharger les fichiers groupés de diagnostic", - "xpack.enterpriseSearch.workplaceSearch.sources.emptyActivity.title": "Il n'existe aucune activité récente", - "xpack.enterpriseSearch.workplaceSearch.sources.event.header": "Événement", - "xpack.enterpriseSearch.workplaceSearch.sources.externalIdentities.link": "API des identités externes", - "xpack.enterpriseSearch.workplaceSearch.sources.feedbackCallOutText": "Vous avez des commentaires sur le déploiement d'un pack de connecteurs ? Faites-nous en part.", - "xpack.enterpriseSearch.workplaceSearch.sources.feedbackLinkLabel": "Vous avez des commentaires sur le déploiement d'un connecteur {name} ? Faites-nous en part.", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.additionalConfigurationNeeded": "Cette source requiert une configuration supplémentaire.", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConfigUpdated": "Mise à jour réussie de la configuration.", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConnected": "Connexion réussie de {sourceName}.", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceNameChanged": "Application réussie du changement de nom en {sourceName}.", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceRemoved": "Suppression réussie de {sourceName}.", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.externalConnectorCreated": "Enregistrement réussi du déploiement du pack de connecteurs.", - "xpack.enterpriseSearch.workplaceSearch.sources.fullSyncDescription": "Récupère tous les documents possibles de la source de contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.fullSyncLabel": "Synchronisation complète", - "xpack.enterpriseSearch.workplaceSearch.sources.groupAccess.title": "Accès du groupe", - "xpack.enterpriseSearch.workplaceSearch.sources.helpText.custom": "Pour créer une source d'API personnalisée, fournissez un nom descriptif et lisible par un humain. Ce nom apparaîtra tel quel dans les diverses expériences de recherche et interfaces de gestion.", - "xpack.enterpriseSearch.workplaceSearch.sources.id.label": "Identificateur de source", - "xpack.enterpriseSearch.workplaceSearch.sources.incrementalSyncDescription": "Récupère les documents/mises à jour depuis la dernière synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.incrementalSyncLabel": "Synchronisation incrémentielle", - "xpack.enterpriseSearch.workplaceSearch.sources.indexingRulesTable.includeEverythingMessage": "Inclure tout le reste à partir de cette source", - "xpack.enterpriseSearch.workplaceSearch.sources.items.header": "Éléments", - "xpack.enterpriseSearch.workplaceSearch.sources.learnCustom.features.button": "Découvrir les fonctionnalités de Platinum", - "xpack.enterpriseSearch.workplaceSearch.sources.learnMore.link": "En savoir plus", - "xpack.enterpriseSearch.workplaceSearch.sources.learnMore.text": "{learnMoreLink} sur les autorisations", - "xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.description": "Pour en savoir plus, contactez votre administrateur d'expérience de recherche.", - "xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.title": "Les sources privées ne sont plus disponibles", - "xpack.enterpriseSearch.workplaceSearch.sources.nextSyncRunningMessage": "dès que le travail en cours se termine", - "xpack.enterpriseSearch.workplaceSearch.sources.noContent.title": "Aucun contenu pour l'instant", - "xpack.enterpriseSearch.workplaceSearch.sources.noContentEmpty.message": "Cette source ne comprend aucun contenu pour l'instant", - "xpack.enterpriseSearch.workplaceSearch.sources.noContentForValue.message": "Aucun résultat pour \"{contentFilterValue}\"", - "xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage": "Source introuvable.", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.accounts": "Comptes", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.allFiles": "Tous les fichiers (dont images, PDF, feuilles de calcul, documents de texte, présentations)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.allStoredFiles": "Tous les fichiers stockés (dont images, vidéos, PDF, feuilles de calcul, documents de texte, présentations)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.articles": "Articles", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.attachments": "Pièces jointes", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.blogPosts": "Articles de blog", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.bugs": "Bugs", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.campaigns": "Campagnes", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.cases": "Cas (y compris les flux et les commentaires)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.contacts": "Contacts", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.directMessages": "Messages directs", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.emails": "E-mails", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.epics": "Epics", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.files": "Fichiers (markdown uniquement)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.folders": "Dossiers", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.gSuiteFiles": "Documents Google G Suite (documents, feuilles, présentations)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.incidents": "Incidents", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.issues": "Problèmes (incluant les commentaires)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.items": "Éléments", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.leads": "Prospects", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.opportunities": "Opportunités", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.pages": "Pages", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.privateMessages": "Messages de canal privé dans lesquels vous êtes un participant actif", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.projects": "Projets", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.publicMessages": "Messages de canal public", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.pullRequests": "Requêtes d'extraction (incluant les commentaires)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.repositoryList": "Liste de référentiels", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.sites": "Sites", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.spaces": "Espaces", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.stories": "Histoires", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.tasks": "Tâches", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.tickets": "Tickets", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.users": "Utilisateurs", - "xpack.enterpriseSearch.workplaceSearch.sources.org.description": "Les sources des organisations sont disponibles pour toute l'organisation et peuvent être attribuées à des groupes d'utilisateurs spécifiques.", - "xpack.enterpriseSearch.workplaceSearch.sources.org.empty.description": "Lorsque des sources de contenu seront partagées avec vous, elles seront affichées ici et seront disponibles via l'expérience de recherche.", - "xpack.enterpriseSearch.workplaceSearch.sources.org.empty.title": "Aucune source de contenu disponible", - "xpack.enterpriseSearch.workplaceSearch.sources.org.link": "Ajouter une source de contenu d'organisation", - "xpack.enterpriseSearch.workplaceSearch.sources.org.title": "Sources d'organisation", - "xpack.enterpriseSearch.workplaceSearch.sources.permissionsSyncDescription": "Récupère tous les changements d'autorisation depuis la dernière synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.permissionsSyncLabel": "Synchronisation des permissions", - "xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.description": "Vérifiez le statut de toutes les sources privées connectées et gérez les sources privées pour votre compte.", - "xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.title": "Gérer les sources de contenu privé", - "xpack.enterpriseSearch.workplaceSearch.sources.private.empty.title": "Vous n'avez aucune source privée", - "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "Les sources de contenu privé sont disponibles uniquement pour vous.", - "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "Mes sources de contenu privé", - "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "Ajouter une source de contenu privé", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateOrg.header.description": "Vous disposez d'un accès aux sources suivantes par le biais {newline} {groups, plural, one {du groupe} other {des groupes}} {groupsSentence}.", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateOrg.header.title": "Sources de contenu organisationnelles", - "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "Vérifiez le statut de toutes les sources partagées avec votre groupe.", - "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "Vérifier les sources de groupe", - "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "Prêt à rechercher", - "xpack.enterpriseSearch.workplaceSearch.sources.remoteSource.label": "Source distante", - "xpack.enterpriseSearch.workplaceSearch.sources.remove.description": "Cette action ne peut pas être annulée.", - "xpack.enterpriseSearch.workplaceSearch.sources.remove.title": "Retirer cette source de contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.description": "Personnalisez le nom de cette source de contenu.", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "Paramètres", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "Nom de la source de contenu", - "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "Vos documents source seront supprimés de Workplace Search.{lineBreak}Voulez-vous vraiment supprimer {name} ?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAddRuleLabel": "Ajouter une règle d'indexation", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAssetsLabel": "Ressources", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsDescription": "Gérez de manière flexible les documents à synchroniser et à rendre disponibles pour la recherche en utilisant les contrôles granulaires ci-dessous.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsLearnMoreLink": "En savoir plus sur les types d'objets de synchronisation.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsDescription": "Ajouter une règle d'indexation pour personnaliser les données qui sont synchronisées à partir de {contentSourceName}. Tout est inclus par défaut, et les documents sont validés par rapport à l'ensemble des règles d'indexation configurées, en commençant par le haut de la liste.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsLabel": "Objets", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableExcludeLabel": "Exclure", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableFileLabel": "Type de fichier", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableIncludeLabel": "Inclure", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableItemLabel": "Article", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePathLabel": "Chemin", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePolicyLabel": "Politique", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableRuleLabel": "Règle", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableValueLabel": "Valeur", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsSyncLearnMoreLink": "En savoir plus sur la personnalisation de vos règles d'indexation.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceBlockedTimeWindowsLinkLabel": "fenêtres de temps bloquées", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.filter.placeholder": "Filtrer le contenu...", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.search.placeholder": "Rechercher du contenu...", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "Contenu de source", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "Explorer la licence Platinum", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "Le niveau de licence de votre organisation a changé. Vos données sont en sécurité, mais les autorisations de niveau document ne sont plus prises en charge et l'interrogation de cette source a été désactivée. Passez à une licence Platinum pour réactiver cette source.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.title": "La source de contenu est désactivée", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceFrequencyDescription": "Gérez la fréquence de la synchronisation des données de Workplace Search vers cette source de contenu. Effectuez des synchronisations plus fréquentes pour garantir la mise à jour de vos données. Synchronisation moins fréquente pour réduire la charge sur les serveurs tiers.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceName.label": "Nom de source", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.box": "Box", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluence": "Confluence", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluenceConnectorPackage": "Pack de connecteurs Confluence", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluenceServer": "Confluence (serveur)", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.custom": "Source d'API personnalisée", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.customConnectorPackage": "Pack de connecteurs personnalisé", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.dropbox": "Dropbox", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.github": "GitHub", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.githubEnterprise": "GitHub Enterprise Server", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.gmail": "Gmail", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.googleDrive": "Google Drive", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.jira": "Jira", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.jiraServer": "Jira (serveur)", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.networkDrive": "Lecteur réseau", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.oneDrive": "OneDrive", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.outlook": "Outlook", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.salesforce": "Salesforce", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.salesforceSandbox": "Sandbox Salesforce", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.serviceNow": "ServiceNow", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePoint": "SharePoint Online", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePointConnectorPackage": "Pack de connecteurs en ligne SharePoint", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePointServer": "Serveur SharePoint", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.slack": "Slack", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.teams": "Équipes", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zendesk": "Zendesk", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zoom": "Effectuer un zoom", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceOverviewTitle": "Aperçu de la source", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSettings.pemKeyPrompText": "Charger un nouveau fichier .pem pour effectuer la rotation de la clé privée", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmMessage": "Êtes-vous sûr de vouloir poursuivre cette demande et d'arrêter toutes les autres synchronisations ?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmTitle": "Démarrer une nouvelle synchronisation de contenu ?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncFrequencyLinkLabel": "fréquence de synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncFrequencyTitle": "Fréquence de synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationButtonLabel": "Contenu de synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationDescription": "Activez ou désactivez la synchronisation des données de cette source de contenu avec Workplace Search.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationFrequencyTitle": "Fréquence de synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationTitle": "Synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleDescription": "Le contenu source sera automatiquement maintenu en synchronisation.", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleLabel": "Synchroniser cette source", - "xpack.enterpriseSearch.workplaceSearch.sources.status.header": "Statut", - "xpack.enterpriseSearch.workplaceSearch.sources.status.heading": "Tout semble parfait", - "xpack.enterpriseSearch.workplaceSearch.sources.status.label": "Statut :", - "xpack.enterpriseSearch.workplaceSearch.sources.status.text": "Vos points de terminaison sont prêts à accepter les requêtes.", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsButton": "Télécharger les données de diagnostic", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsDescription": "Récupérez les données de diagnostic pertinentes pour résoudre les problèmes liés aux processus de synchronisation active.", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsTitle": "Diagnostic de synchronisation", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationCallout": "Configurez {syncFrequencyLink} ou {blockTimeWindowsLink}.", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationDisabledDescription": "Contactez votre administrateur pour activer les contrôles de synchronisation.", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationDisabledTitle": "La synchronisation de la source est désactivée.", - "xpack.enterpriseSearch.workplaceSearch.sources.syncSettingsUpdatedMessage": "Mise à jour des paramètres de synchronisation de la source.", - "xpack.enterpriseSearch.workplaceSearch.sources.syncUnsavedChangesMessage": "Vos modifications n'ont pas été enregistrées. Voulez-vous vraiment quitter ?", - "xpack.enterpriseSearch.workplaceSearch.sources.time.header": "Heure", - "xpack.enterpriseSearch.workplaceSearch.sources.totalDocuments.label": "Total des documents", - "xpack.enterpriseSearch.workplaceSearch.sources.understandButton": "Je comprends", - "xpack.enterpriseSearch.workplaceSearch.sources.utcLabel": "Heure UTC actuelle : {utcTime}", - "xpack.enterpriseSearch.workplaceSearch.sources.utcTitle": "Toutes les heures sont en UTC", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addFirstSources.button": "Ajouter des sources", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addMoreSources.button": "Ajouter d'autres sources", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description": "Vous avez ajouté {sourcesCount, number} {sourcesCount, plural, one {source organisationnelle} other {sources organisationnelles}}. Bonne recherche.", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.docPermissions.description": "Les documents ne seront pas interrogeables à partir de Workplace Search tant que les mappings d'utilisateur et de groupe n'auront pas été configurés. {documentPermissionsLink}.", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading": "{addedSourceName} requiert une configuration supplémentaire", - "xpack.enterpriseSearch.workplaceSearch.statusPopoverTooltip": "Cliquer pour afficher les informations", - "xpack.enterpriseSearch.workplaceSearch.title": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.update.label": "Mettre à jour", - "xpack.enterpriseSearch.workplaceSearch.url.label": "URL", "xpack.eventLog.savedObjectProviderRegistry.getProvidersClient.noDefaultProvider": "Le log d'événements requiert un fournisseur par défaut.", "xpack.exploratoryView..synthetics.addDataButtonLabel": "Ajouter des données synthétiques", "xpack.exploratoryView.alerts.alertStarted": "Horodatage", @@ -32208,17 +30446,6 @@ "xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName": "Non affecté", "xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel": "Principal non affecté", "xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel": "Réplique non affectée", - "xpack.monitoring.entSearch.overview.appSearchEngines": "Moteurs App Search", - "xpack.monitoring.entSearch.overview.appSearchSummary": "Résumé App Search", - "xpack.monitoring.entSearch.overview.heading": "Aperçu Enterprise Search", - "xpack.monitoring.entSearch.overview.instances": "Instances", - "xpack.monitoring.entSearch.overview.lowLevelSummary": "Résumé d'utilisation des ressources de bas niveau", - "xpack.monitoring.entSearch.overview.networkingSummary": "Résumé du trafic réseau", - "xpack.monitoring.entSearch.overview.pageTitle": "Aperçu Enterprise Search", - "xpack.monitoring.entSearch.overview.routeTitle": "Enterprise Search - Aperçu", - "xpack.monitoring.entSearch.overview.workplaceSearchOrgSources": "Sources de contenu d'organisation", - "xpack.monitoring.entSearch.overview.workplaceSearchPrivateSources": "Sources de contenu privées", - "xpack.monitoring.entSearch.overview.workplaceSearchSummary": "Résumé Workplace Search", "xpack.monitoring.errors.connectionErrorMessage": "Erreur de connexion : vérifiez la connexion réseau du cluster de monitoring Elasticsearch et reportez-vous aux logs Kibana pour en savoir plus.", "xpack.monitoring.errors.insufficientUserErrorMessage": "Autorisations d'utilisateur insuffisantes pour les données de monitoring", "xpack.monitoring.errors.invalidAuthErrorMessage": "Authentification non valide pour le cluster de monitoring", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 225937ebae1b4..356ae5390fd39 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -3823,7 +3823,6 @@ "guidedOnboarding.quitGuideModal.quitButtonLabel": "ガイドを終了", "guidedOnboardingPackage.gettingStarted.cards.aiSearch.title": "AIを活用した検索エクスペリエンスの構築", "guidedOnboardingPackage.gettingStarted.cards.apmObservability.title": "アプリケーション{lineBreak}パフォーマンスを監視(APM / トレース)", - "guidedOnboardingPackage.gettingStarted.cards.appSearch.title": "Elasticsearchの上に{lineBreak}アプリケーションを構築", "guidedOnboardingPackage.gettingStarted.cards.cloudSecurity.title": "クラウドセキュリティ態勢管理(CSPM)でクラウド{lineBreak}資産を保護", "guidedOnboardingPackage.gettingStarted.cards.completeLabel": "ガイド完了", "guidedOnboardingPackage.gettingStarted.cards.databaseSearch.title": "データベースと{lineBreak}ビジネスシステムで検索", @@ -15982,923 +15981,11 @@ "xpack.enterpriseSearch.apiKeyConfig.newApiKeyCreatedCalloutLabel": "新しいAPIキーが正常に作成されました", "xpack.enterpriseSearch.applications.navTitle": "ビルド", "xpack.enterpriseSearch.applications.productName": "アプリケーション", - "xpack.enterpriseSearch.appSearch.actions.restoreDefaultsButonLabel": "デフォルトを復元", - "xpack.enterpriseSearch.appSearch.adminRoleTypeDescription": "アカウント設定の管理を除き、管理者はすべての操作を実行できます。", - "xpack.enterpriseSearch.appSearch.allEnginesDescription": "すべてのエンジンへの割り当てには、後から作成および管理されるすべての現在および将来のエンジンが含まれます。", - "xpack.enterpriseSearch.appSearch.allEnginesLabel": "すべてのエンジンに割り当て", - "xpack.enterpriseSearch.appSearch.analystRoleTypeDescription": "アナリストは、ドキュメント、クエリーテスト、分析のみを表示できます。", - "xpack.enterpriseSearch.appSearch.audit.title": "監査", - "xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.confirmationPopupMessage": "ドメイン\"{domainUrl}\"とすべての設定を削除しますか?", - "xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.successMessage": "ドメイン''{domainUrl}'が削除されました", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.description": "複数のドメインをこのエンジンのWebクローラーに追加できます。ここで別のドメインを追加して、[管理]ページからエントリポイントとクロールルールを変更します。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.openButtonLabel": "ドメインを追加", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.title": "新しいドメインを追加", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationFailureMessage": "[インデックス制限]チェックが失敗したため、コンテンツを検証できません。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationLabel": "コンテンツ検証", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.entryPointLabel": "Webクローラーエントリポイントが{entryPointValue}として設定されました", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.errorsTitle": "何か問題が発生しましたエラーを解決して、再試行してください。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationDescription": "上記のエラーが解決されるまで、Webクローラーはこのドメインのコンテンツにインデックスを作成できません。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationTitle": "検証の失敗を無視して続行", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsFailureMessage": "[ネットワーク接続]チェックが失敗したため、インデックス制限を判定できません。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsLabel": "インデックスの制約", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.initialVaidationLabel": "初期検証", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityFailureMessage": "[初期検証]チェックが失敗したため、ネットワーク接続を確立できません。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityLabel": "ネットワーク接続", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.submitButtonLabel": "ドメインを追加", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.testUrlButtonLabel": "ブラウザーでURLをテスト", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.unexpectedValidationErrorMessage": "予期しないエラー", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlHelpText": "ドメインURLにはプロトコルが必要です。パスを含めることはできません。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlLabel": "ドメインURL", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.validateButtonLabel": "ドメインを検証", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel": "自動的にクロール", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix": "毎", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.formDescription": "ご安心ください。クロールは自動的に開始されます。{readMoreMessage}。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.readMoreLink": "詳細をお読みください。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleDescription": "クロールスケジュールはこのエンジンのすべてのドメインに適用されます。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleFrequencyLabel": "スケジュール頻度", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleUnitsLabel": "スケジュール時間単位", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage": "自動クローリングが無効にされました。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage": "自動クローリングスケジュールが更新されました。", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{domainCount, plural, other {# 件のドメイン}}で{crawlType}クロール", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlDepthLabel": "最大クロール深度", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlTypeLabel": "クロールタイプ", - "xpack.enterpriseSearch.appSearch.crawler.configurationDocumentationLinkDescription": "Kibanaでのクローラーログの構成の詳細をご覧ください", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.customEntryPointUrlsTextboxLabel": "カスタム入力ポイントURL", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.customSitemapUrlsTextboxLabel": "カスタムサイトマップURL", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.domainsAccordionButtonLabel": "ドメインをクロールに追加", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.emptyDomainsMessage": "ドメインを選択してください。", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.entryPointsTabLabel": "エントリポイント", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeaderDescription": "カスタム設定でワンタイムクロールを設定します。", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeadTitle": "カスタムクロール構成", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "{robotsDotTxt}で検出されたサイトマップを含める", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldDescription": "クローラーが走査するページの数を指定する最大クロール深度を設定します。クロールをエントリポイントのみに制限する場合は、値を1に設定します。", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldLabel": "最大クロール深度", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.seedUrlsAccordionButtonLabel": "シードURL", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.selectedDescriptor": "選択済み", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.sitemapsTabLabel": "サイトマップ", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.startCrawlButtonLabel": "今すぐ適用してクロール", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.previewTabLabel": "プレビュー", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.rawJSONTabLabel": "元のJSON", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.title": "クロールリクエスト詳細", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.domainsTitle": "ドメイン", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.seedUrlsTitle": "シードURL", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.sitemapUrlsTitle": "サイトマップURL", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.avgResponseTimeLabel": "平均応答", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.clientErrorsLabel": "4xxエラー", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.durationTooltipTitle": "期間", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.logsDisabledMessage": "詳細なクロール統計情報については、設定でWebクローラーログを有効にします。", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesTooltip": "クロール中にアクセスされ抽出されたページ。", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesTooltipTitle": "アクセスされたページ", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesVisitedTooltipTitle": "ページ", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.serverErrorsLabel": "5xxエラー", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.urlsTooltip": "クロール中にクローラーによって検出されたURL(クロール構成のため従われなかったURLを含む)。", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.urlsTooltipTitle": "検出されたURL", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusBanner.changesCalloutTitle": "行った変更は次回のクロールの開始まで適用されません。", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.cancelCrawlMenuItemLabel": "クロールをキャンセル", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.crawlingButtonLabel": "クロール中...", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.pendingButtonLabel": "保留中...", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.retryCrawlButtonLabel": "クロールを再試行", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.showSelectedFieldsButtonLabel": "選択したフィールドのみを表示", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startACrawlButtonLabel": "クロールを開始", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startingButtonLabel": "開始中...", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.stoppingButtonLabel": "停止中...", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceled": "キャンセル", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceling": "キャンセル中", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.failed": "失敗", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.pending": "保留中", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.running": "実行中", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.skipped": "スキップ", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.starting": "開始中", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.success": "成功", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspended": "一時停止", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspending": "一時停止中", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsDescription": "最近のクロールリクエストはここに記録されます。各クロールのリクエストIDを使用すると、KibanaのDiscoverまたはログユーザーインターフェースで、進捗状況を追跡し、クロールイベントを検査できます。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.crawlType": "クロールタイプ", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.created": "作成済み", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domains": "ドメイン", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domainURL": "リクエストID", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.status": "ステータス", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.body": "まだクロールを開始していません。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.title": "最近のクロールリクエストがありません", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTitle": "最近のクロールリクエスト", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.beginsWithLabel": "で開始", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.containsLabel": "を含む", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.endsWithLabel": "で終了", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.regexLabel": "正規表現", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.allowLabel": "許可", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.disallowLabel": "禁止", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.addButtonLabel": "クロールルールを追加", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.deleteSuccessToastMessage": "クロールルールが削除されました。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.description": "URLがルールと一致するページを含めるか除外するためのクロールルールを作成します。ルールは連続で実行されます。各URLは最初の一致に従って評価されます。{link}", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.descriptionLinkText": "クロールルールの詳細", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTableHead": "パスパターン", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTooltip": "パスパターンはアスタリスク(*)を除くリテラル文字列です。アスタリスクはいずれかと一致するメタ文字です。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.policyTableHead": "ポリシー", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.regexPathPatternTooltip": "パスパターンは、Ruby言語正規表現エンジンと互換性がある正規表現です。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.ruleTableHead": "ルール", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.title": "クロールルール", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.modalHeaderTitle": "選択したドメインをクロール", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.selectedDescriptor": "選択済み", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.startCrawlButtonLabel": "今すぐ適用してクロール", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.full": "完全", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.partial": "部分", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.reAppliedCrawlRules": "再適用されたクロールルール", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.allFieldsLabel": "すべてのフィールド", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.description": "Webクローラーは一意のページにのみインデックスします。重複するページを検討するときにクローラーが使用するフィールドを選択します。すべてのスキーマフィールドを選択解除して、このドメインで重複するドキュメントを許可します。{documentationLink}。", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.learnMoreMessage": "コンテンツハッシュの詳細", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.resetToDefaultsButtonLabel": "デフォルトにリセット", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.selectedFieldsLabel": "選択したフィールド", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.showAllFieldsButtonLabel": "すべてのフィールドを表示", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.title": "ドキュメント処理を複製", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.cannotUndoMessage": "これは元に戻せません。", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.deleteDomainButtonLabel": "ドメインを削除", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.description": "このドメインをクローラーから削除します。これにより、設定したすべてのエントリポイントとクロールルールも削除されます。{cannotUndoMessage}。", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.title": "ドメインを削除", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.add.successMessage": "ドメイン''{domainUrl}'が正常に追加されました", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.buttonLabel": "このドメインを削除", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.manage.buttonLabel": "このドメインを管理", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.actions": "アクション", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.documents": "ドキュメント", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.domainURL": "ドメインURL", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.lastActivity": "前回のアクティビティ", - "xpack.enterpriseSearch.appSearch.crawler.domainsTitle": "ドメイン", - "xpack.enterpriseSearch.appSearch.crawler.empty.crawlerDocumentationLinkDescription": "Webクローラーの詳細を参照してください", - "xpack.enterpriseSearch.appSearch.crawler.empty.description": "Webサイトのコンテンツに簡単にインデックスします。開始するには、ドメイン名を入力し、任意のエントリポイントとクロールルールを指定します。その他の手順は自動的に行われます。", - "xpack.enterpriseSearch.appSearch.crawler.empty.title": "開始するドメインを追加", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.addButtonLabel": "エントリポイントを追加", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.description": "ここではWebサイトの最も重要なURLを含めます。エントリポイントURLは、他のページへのリンク目的で最初にインデックスおよび処理されるページです。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageDescription": "クローラーのエントリポイントを指定するには、{link}してください", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageLinkText": "エントリポイントを追加", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageTitle": "既存のエントリポイントがありません。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.lastItemMessage": "クローラーには1つ以上のエントリポイントが必要です。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText": "エントリポイントの詳細。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.title": "エントリポイント", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.urlTableHead": "URL", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingButtonLabel": "自動クローリング", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingTitle": "自動クローリング", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.manageCrawlsButtonLabel": "クロールの管理", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRules.successMessage": "クロールルールはバックグラウンドで再適用されています", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRulesButtonLabel": "クロールルールを再適用", "xpack.enterpriseSearch.appSearch.crawler.simplifiedSelectable.deselectAllButtonLabel": "すべて選択解除", "xpack.enterpriseSearch.appSearch.crawler.simplifiedSelectable.selectAllButtonLabel": "すべて選択", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.addButtonLabel": "サイトマップを追加", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.deleteSuccessToastMessage": "サイトマップが削除されました。", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.description": "このドメインのクローラーのサイトマップURLを指定します。", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.emptyMessageTitle": "既存のサイトマップがありません。", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.title": "サイトマップ", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.urlTableHead": "URL", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlAllDomainsMenuLabel": "このエンジンのすべてのドメインをクロール", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlCustomSettingsMenuLabel": "カスタム設定でクロール", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlSelectDomainsMenuLabel": "選択したドメインをクロール", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.startACrawlButtonLabel": "クロールを開始", - "xpack.enterpriseSearch.appSearch.credentials.apiEndpoint": "エンドポイント", - "xpack.enterpriseSearch.appSearch.credentials.apiKeys": "APIキー", - "xpack.enterpriseSearch.appSearch.credentials.copied": "コピー完了", - "xpack.enterpriseSearch.appSearch.credentials.copyApiEndpoint": "API エンドポイントをクリップボードにコピーします。", - "xpack.enterpriseSearch.appSearch.credentials.copyApiKey": "API キーをクリップボードにコピー", - "xpack.enterpriseSearch.appSearch.credentials.createKey": "キーを作成", - "xpack.enterpriseSearch.appSearch.credentials.deleteKey": "API キーの削除", - "xpack.enterpriseSearch.appSearch.credentials.documentationLink1": "キーの詳細については、ドキュメントを", - "xpack.enterpriseSearch.appSearch.credentials.documentationLink2": "ご覧ください。", - "xpack.enterpriseSearch.appSearch.credentials.editKey": "API キーの編集", - "xpack.enterpriseSearch.appSearch.credentials.empty.body": "App SearchがElasticにアクセスすることを許可します。", - "xpack.enterpriseSearch.appSearch.credentials.empty.buttonLabel": "APIキーの詳細", - "xpack.enterpriseSearch.appSearch.credentials.empty.title": "最初のAPIキーを作成", - "xpack.enterpriseSearch.appSearch.credentials.flyout.createTitle": "新規キーを作成", - "xpack.enterpriseSearch.appSearch.credentials.flyout.updateTitle": "{tokenName} を更新", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.helpText": "キーがアクセスできるエンジン:", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.label": "エンジンを選択", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.helpText": "すべての現在のエンジンと将来のエンジンにアクセスします。", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.label": "完全エンジンアクセス", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.label": "エンジンアクセス制御", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.helpText": "キーアクセスを特定のエンジンに制限します。", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.label": "限定エンジンアクセス", - "xpack.enterpriseSearch.appSearch.credentials.formName.helpText": "キーの名前が作成されます:{name}", - "xpack.enterpriseSearch.appSearch.credentials.formName.label": "キー名", - "xpack.enterpriseSearch.appSearch.credentials.formName.placeholder": "例:my-engine-key", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.helpText": "非公開 API キーにのみ適用されます。", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.label": "読み書きアクセスレベル", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.readLabel": "読み取りアクセス", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.writeLabel": "書き込みアクセス", - "xpack.enterpriseSearch.appSearch.credentials.formType.label": "キータイプ", - "xpack.enterpriseSearch.appSearch.credentials.hideApiKey": "API キーを非表示", - "xpack.enterpriseSearch.appSearch.credentials.list.enginesTitle": "エンジン", - "xpack.enterpriseSearch.appSearch.credentials.list.keyTitle": "キー", - "xpack.enterpriseSearch.appSearch.credentials.list.modesTitle": "モード", - "xpack.enterpriseSearch.appSearch.credentials.list.nameTitle": "名前", - "xpack.enterpriseSearch.appSearch.credentials.list.typeTitle": "型", - "xpack.enterpriseSearch.appSearch.credentials.showApiKey": "API キーを表示", - "xpack.enterpriseSearch.appSearch.credentials.title": "資格情報", - "xpack.enterpriseSearch.appSearch.credentials.updateWarning": "既存の API キーはユーザー間で共有できます。このキーのアクセス権を変更すると、このキーにアクセスできるすべてのユーザーに影響します。", - "xpack.enterpriseSearch.appSearch.credentials.updateWarningTitle": "十分ご注意ください!", - "xpack.enterpriseSearch.appSearch.cta": "探索", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonDescription": "このクエリーの候補を有効にする", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonLabel": "許可", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.allowQuerySuccessMessage": "このクエリーの将来の候補が通知されます", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.description": "このクエリーの候補は通知されません", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.queryColumnName": "クエリー", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.refresh": "更新", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.title": "無視されたクエリー", - "xpack.enterpriseSearch.appSearch.curations.settings.acceptNewSuggesstionsSwitchLabel": "新しい候補を自動的に承諾", - "xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutDescription": "適応型関連性では、アカウントで分析を有効にする必要があります。", - "xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutTitle": "分析が無効です", - "xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsDescription": "App Searchはエンジンの分析を監視し、キュレーションの変更を提案して、最も関連度の高い結果を提供できるようにします。各候補は、承諾、却下、修正できます。", - "xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsTitle": "適応型関連性によるキュレーション", - "xpack.enterpriseSearch.appSearch.curations.settings.enableautomaticCurationsSwitchLabel": "候補を有効にする", - "xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTASubtitle": "{platinumLicenseName}サブスクリプションをアップグレードし、機械学習の能力を高めます。エンジンの分析を解析することで、App Searchは新規または更新されたキュレーションを提案できます。ユーザーが検索している正確な内容を見つけられるように簡単に支援できます。今すぐ無料トライアルを開始してください。", - "xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTATitle": "適応型関連性によるキュレーションの概要", - "xpack.enterpriseSearch.appSearch.curations.settings.manageAnalyticsButtonLabel": "分析の管理", - "xpack.enterpriseSearch.appSearch.curations.settings.platinum": "プラチナ", - "xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION": "開発者はエンジンのすべての要素を管理できます。", - "xpack.enterpriseSearch.appSearch.documentCreation.api.description": "{documentsApiLink}を使用すると、新しいドキュメントをエンジンに追加できるほか、ドキュメントの更新、IDによるドキュメントの取得、ドキュメントの削除が可能です。基本操作を説明するさまざまな{clientLibrariesLink}があります。", - "xpack.enterpriseSearch.appSearch.documentCreation.api.example": "実行中のAPIを表示するには、コマンドラインまたはクライアントライブラリを使用して、次の要求の例で実験することができます。", - "xpack.enterpriseSearch.appSearch.documentCreation.api.title": "APIでインデックス", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.apiDescription": "ドキュメントエンドポイントにPOST", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.apiTitle": "API からインデックス", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.crawlDescription": "URLから自動的にドキュメントにインデックス", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.crawlTitle": "Crawler を使用", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterLink": "ドキュメンテーションを表示", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterText": "ドキュメントのインデックスの詳細", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateIllustrationAltText": "図解", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateTitle": "ドキュメントを追加", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.jsonDescription": "元のJSONを貼り付けるかアップロードして、ドキュメントを追加", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.jsonTitle": "JSONを貼り付けまたはアップロード", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.button": "インデックスに接続", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.description": "エンタープライズ サーチUIを使用して、既存のElasticsearchインデックスに直接接続し、データを検索可能かつチューニング可能にできるようになりました。{learnMoreLink}", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.link": "既存のインデックスの使用の詳細", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.title": "Elasticsearchインデックスを関連付け", - "xpack.enterpriseSearch.appSearch.documentCreation.errorsTitle": "何か問題が発生しましたエラーを解決して、再試行してください。", - "xpack.enterpriseSearch.appSearch.documentCreation.helperText": "ドキュメントをインデックスのためにエンジンに送信するには、3つの方法があります。JSONファイルの貼り付けやアップロード、ドキュメントAPIエンドポイントへのPOST、Elastic Webクローラーの使用によって、URLから自動的にドキュメントにインデックスを作成できます。", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.pasteTabName": "貼り付け", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.title": "JSONを貼り付けまたはアップロード", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.uploadTabName": "アップロード", - "xpack.enterpriseSearch.appSearch.documentCreation.largeFile": "非常に大きいファイルをアップロードしています。ブラウザーがロックされたり、処理に非常に時間がかかったりする可能性があります。可能な場合は、データを複数の小さいファイルに分割してください。", - "xpack.enterpriseSearch.appSearch.documentCreation.noFileFound": "ファイルが見つかりません。", - "xpack.enterpriseSearch.appSearch.documentCreation.notValidJson": "ドキュメントの内容は、有効なJSON配列またはオブジェクトでなければなりません。", - "xpack.enterpriseSearch.appSearch.documentCreation.noValidFile": "ファイル解析の問題。", - "xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.description": "JSONドキュメントの配列を貼り付けます。JSONが有効であり、各ドキュメントオブジェクトが{maxDocumentByteSize}バイト未満であることを確認してください。", - "xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.label": "ここにJSONを貼り付け", - "xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title": "新しいドキュメントの追加", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.documentNotIndexed": "このドキュメントにはインデックスが作成されていません。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.fixErrors": "エラーを修正してください", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.invalidDocuments": "エラーがある{invalidDocuments, number} {invalidDocuments, plural, other {個のドキュメント}}...", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.newDocuments": "{newDocuments, number} {newDocuments, plural, other {個のドキュメント}}を追加しました。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.newSchemaFields": "{newFields, number} {newFields, plural, other {個のフィールド}}をエンジンのスキーマに追加しました。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.noNewDocuments": "新しいドキュメントはありません。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.noNewSchemaFields": "新しいスキーマフィールドはありません。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.otherDocuments": "と{documents, number} other {documents, plural, other {個のドキュメント}}。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.title": "インデックス概要", - "xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.label": ".jsonファイルがある場合は、ドラッグアンドドロップするか、アップロードします。JSONが有効であり、各ドキュメントオブジェクトが{maxDocumentByteSize}バイト未満であることを確認してください。", - "xpack.enterpriseSearch.appSearch.documentCreation.warningsTitle": "警告!", - "xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete": "このドキュメントを削除しますか?", - "xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess": "ドキュメントは削除されました", - "xpack.enterpriseSearch.appSearch.documentDetail.fieldHeader": "フィールド", - "xpack.enterpriseSearch.appSearch.documentDetail.title": "ドキュメント:{documentId}", - "xpack.enterpriseSearch.appSearch.documentDetail.valueHeader": "値", - "xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout": "エンジンは{elasticsearchIndexName}に接続されています。Kibanaでこのインデックスのデータを修正できます。", - "xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout.title": "このエンジンのデータはElasticsearchで管理されています。", - "xpack.enterpriseSearch.appSearch.documents.empty.description": "JSONをアップロードするか、APIを使用して、App Search Web Crawlerを使用して、ドキュメントをインデックスできます。", - "xpack.enterpriseSearch.appSearch.documents.empty.title": "最初のドキュメントを追加", - "xpack.enterpriseSearch.appSearch.documents.indexDocuments": "ドキュメントのインデックスを作成", - "xpack.enterpriseSearch.appSearch.documents.metaEngineCallout": "メタエンジンには多数のソースエンジンがあります。ドキュメントを変更するには、スコアエンジンにアクセスしてください。", - "xpack.enterpriseSearch.appSearch.documents.metaEngineCallout.title": "メタエンジンにいます。", - "xpack.enterpriseSearch.appSearch.documents.paging.ariaLabelBottom": "画面の下部にある検索結果のページ制御", - "xpack.enterpriseSearch.appSearch.documents.paging.ariaLabelTop": "画面の上部にある検索結果のページ制御", - "xpack.enterpriseSearch.appSearch.documents.search.ariaLabel": "ドキュメントのフィルター", - "xpack.enterpriseSearch.appSearch.documents.search.customizationButton": "フィルターをカスタマイズして並べ替える", - "xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.button": "カスタマイズ", - "xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.message": "ドキュメント検索エクスペリエンスをカスタマイズできることをご存知ですか。次の[カスタマイズ]をクリックすると開始します。", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFields": "取得された値はフィルターとして表示され、クエリーの絞り込みで使用できます", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFieldsLabel": "フィールドのフィルタリング", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.sortFields": "結果の並べ替えオプション(昇順と降順)を表示するために使用されます", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.sortFieldsLabel": "フィールドの並べ替え", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.title": "ドキュメント検索のカスタマイズ", - "xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.noValue.selectOption": "", - "xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.showMore": "詳細表示", - "xpack.enterpriseSearch.appSearch.documents.search.noResults": "「{resultSearchTerm}」の結果がありません。", - "xpack.enterpriseSearch.appSearch.documents.search.placeholder": "ドキュメントのフィルター...", - "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "1 ページに表示する結果数", - "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "表示:", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy": "並べ替え基準", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.ariaLabel": "結果の並べ替え条件", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.ascendingDropDownOptionLabel": "{fieldName}(昇順)", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.descendingDropDownOptionLabel": "{fieldName}(降順)", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.documentId": "ドキュメントID", - "xpack.enterpriseSearch.appSearch.documents.title": "ドキュメント", - "xpack.enterpriseSearch.appSearch.editorRoleTypeDescription": "エディターは検索設定を管理できます。", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateButton": "インデックスの管理", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateIllustrationAltText": "図解", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateTitle": "ドキュメントをインデックスに追加", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.helperText": "Elasticsearchインデックスの{elasticsearchIndexName}にはまだドキュメントがありません。Kibanaでインデックス管理を開き、Elasticsearchを変更します。", - "xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta": "エンジンを作成", - "xpack.enterpriseSearch.appSearch.emptyState.description1": "App Searchエンジンは、検索エクスペリエンスのために、ドキュメントを格納します。", - "xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description": "App Search管理者に問い合わせ、エンジンへのアクセスを作成するか、付与するように依頼してください。", - "xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title": "エンジンがありません", - "xpack.enterpriseSearch.appSearch.emptyState.title": "初めてのエンジンの作成", - "xpack.enterpriseSearch.appSearch.engine.analytics.allTagsDropDownOptionLabel": "すべての分析タグ", - "xpack.enterpriseSearch.appSearch.engine.analytics.clickTablesDescription": "クリック数が最も多いクエリーと最も少ないクエリーを検出します。", - "xpack.enterpriseSearch.appSearch.engine.analytics.clickTablesTitle": "クリック分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.applyButtonLabel": "フィルターを適用", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel": "終了日でフィルター", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel": "開始日でフィルター", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.tagAriaLabel": "分析タグでフィルター\"", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.cardDescription": "{queryTitle}のクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.chartTooltip": "1日あたりのクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.tableDescription": "このクエリーの結果のうち最もクリック数が多いドキュメント。", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.tableTitle": "上位のクリック", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title": "クエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchButtonLabel": "詳細を表示", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchPlaceholder": "検索語に移動", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryTablesDescription": "最も頻繁に実行されたクエリーと、結果を返さなかったクエリーに関する洞察が得られます。", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryTablesTitle": "クエリー分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesDescription": "現在実行中のクエリーを表示します。", - "xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesTitle": "最近のクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.clicksColumn": "クリック", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.editTooltip": "キュレーションを管理", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksDescription": "このクエリーからクリックされたドキュメントはありません。", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksTitle": "クリックなし", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesDescription": "この期間中にはクエリーが実行されませんでした。", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesTitle": "表示するクエリーがありません", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesDescription": "クエリーは受信されたときにここに表示されます。", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesTitle": "最近のクエリーなし", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.moreTagsBadge": "と{moreTagsCount}以上", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.queriesColumn": "クエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.resultsColumn": "結果", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsColumn": "分析タグ", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsCountBadge": "{tagsCount, plural, other {#個のタグ}}", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.termColumn": "検索語", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.timeColumn": "時間", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAction": "表示", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAllButtonLabel": "すべて表示", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewTooltip": "クエリー分析を表示", - "xpack.enterpriseSearch.appSearch.engine.analytics.title": "分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoClicksTitle": "クリックがない上位のクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoResultsTitle": "結果がない上位のクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesTitle": "上位のクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesWithClicksTitle": "クリックがある上位のクエリー", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations": "合計 API 処理数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks": "合計クリック数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments": "合計ドキュメント数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries": "クエリー合計", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalQueriesNoResults": "結果がない上位のクエリー", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.detailsButtonLabel": "詳細", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel": "API参照を表示", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyDescription": "API要求が発生したときにリアルタイムでログが更新されます。", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyTitle": "過去24時間にはAPIイベントがありません", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.endpointTableHeading": "エンドポイント", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.flyout.title": "リクエスト詳細", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTableHeading": "メソド", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTitle": "メソド", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorDescription": "接続を確認するか、手動でページを読み込んでください。", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorMessage": "APIログデータを更新できませんでした", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.recent": "最近の API イベント", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.requestBodyTitle": "リクエスト本文", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.requestPathTitle": "リクエストパス", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.responseBodyTitle": "応答本文", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTableHeading": "ステータス", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTitle": "ステータス", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.timestampTitle": "タイムスタンプ", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.timeTableHeading": "時間", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.title": "API ログ", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.userAgentTitle": "ユーザーエージェント", - "xpack.enterpriseSearch.appSearch.engine.crawler.title": "Webクローラー", - "xpack.enterpriseSearch.appSearch.engine.curation.automatedLabel": "自動", - "xpack.enterpriseSearch.appSearch.engine.curation.convertToManualCurationButtonLabel": "手動キュレーションに変換", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyButtonLabel": "履歴", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription": "適応型関連性による最近のキュレーションの変更の詳細ログ。", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle": "順応型関連性の変更", - "xpack.enterpriseSearch.appSearch.engine.curation.suggestedDocumentsCallout.description": "エンジンの分析に基づいて、レビュー可能な新しい提案されたドキュメント昇格があります。", - "xpack.enterpriseSearch.appSearch.engine.curation.suggestedDocumentsCallout.title": "このクエリーの新しい提案されたドキュメント", - "xpack.enterpriseSearch.appSearch.engine.curations.actionsPopoverAriaLabel": "その他の提案アクション", - "xpack.enterpriseSearch.appSearch.engine.curations.activeQueryHelpText": "オーガニック検索結果を表示するクエリーを選択", - "xpack.enterpriseSearch.appSearch.engine.curations.activeQueryLabel": "アクティブなクエリー", - "xpack.enterpriseSearch.appSearch.engine.curations.addQueryButtonLabel": "クエリーを追加", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel": "結果を手動で追加", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchEmptyDescription": "一致するコンテンツが見つかりません。", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder": "検索エンジンドキュメント", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.title": "結果をキュレーションに追加", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.queryColumnHeader": "クエリー", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableDecription": "適応型関連性による最近のキュレーションの変更の詳細ログ。", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableTitle": "順応型関連性の変更", - "xpack.enterpriseSearch.appSearch.engine.curations.convertToManualCurationConfirmation": "これを手動キュレーションに変換しますか?", - "xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesDescription": "キュレーションする1つ以上のクエリーを追加します。後からその他のクエリーを追加または削除できます。", - "xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesTitle": "キュレーションクエリー", - "xpack.enterpriseSearch.appSearch.engine.curations.create.title": "キューレーションを作成", - "xpack.enterpriseSearch.appSearch.engine.curations.deleteConfirmation": "このキュレーションを削除しますか?", - "xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage": "キュレーションが削除されました", - "xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel": "この結果を降格", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel": "キュレーションガイドを読む", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.description": "キュレーションを使用して、ドキュメントを昇格させるか非表示にします。最も検出させたい内容をユーザーに検出させるように支援します。", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.noCurationsTitle": "最初のキュレーションを作成", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription": "上記のオーガニック結果の目アイコンをクリックしてドキュメントを非表示にするか、結果を手動で検索して非表示にします。", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle": "まだドキュメントを非表示にしていません", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel": "すべて非表示", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title": "非表示の結果", - "xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel": "この結果を非表示にする", - "xpack.enterpriseSearch.appSearch.engine.curations.historyPageTabLabel": "履歴", - "xpack.enterpriseSearch.appSearch.engine.curations.manage.title": "キュレーションを管理", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel": "クエリーを管理", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryDescription": "このキュレーションのクエリーを編集、追加、削除します。", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryTitle": "クエリーを管理", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.description": "表示するオーガニック結果はありません。{manualDescription}", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.manualDescription": "上記のアクティブなクエリーを追加または変更します。", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.title": "\"{currentQuery}\"の上位のオーガニックドキュメント", - "xpack.enterpriseSearch.appSearch.engine.curations.overview.title": "キュレーションされた結果", - "xpack.enterpriseSearch.appSearch.engine.curations.overviewPageTabLabel": "概要", - "xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel": "この結果を昇格", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.automatedEmptyDescription": "昇格するドキュメントが特定されていません", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.emptyDescription": "以下のオーガニック結果からドキュメントにスターを付けるか、手動で結果を検索して昇格します。", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.managedByAppSearchDescription": "このキュレーションはApp Searchで自動化されています", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel": "すべて降格", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title": "昇格された結果", - "xpack.enterpriseSearch.appSearch.engine.curations.queryPlaceholder": "クエリーを入力", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.queryColumnHeader": "クエリー", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableDescription": "以前に却下した候補を表示します。", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableTitle": "最近却下した候補", - "xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation": "変更を消去して、デフォルトの結果に戻りますか?", - "xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription": "スターをクリックして結果を昇格し、目をクリックして非表示にします。", - "xpack.enterpriseSearch.appSearch.engine.curations.settingsPageTabLabel": "設定", - "xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel": "この結果を表示", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.acceptButtonLabel": "承諾", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAcceptButtonLabel": "この候補を承諾", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAutomateButtonLabel": "自動化 - このクエリーの新しい候補を常に承諾", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsPopoverTitle": "候補の管理", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsRejectButtonLabel": "この候補を却下", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsTurnOffButtonLabel": "このクエリーの候補を却下してオフにする", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.breadcrumbLabel": "候補:{query}", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.collapseButtonLabel": "オーガニック検索結果を折りたたむ", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.currentTitle": "現在", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.expandButtonLabel": "オーガニック検索結果を展開", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsDescription": "このクエリーのオーガニック検索結果は返されませんでした", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsTitle": "0件ヒット", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noResultsMessage": "現在このクエリーの昇格されたドキュメントはありません", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.notFoundError": "候補が見つかりませんでした。すでに適用されているか却下されている可能性があります。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.rejectButtonLabel": "却下", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelDescription": "このキュレーションはApp Searchで自動化できます", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelTitle": "昇格された結果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAppliedMessage": "候補は正常に適用されました。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAutomatedMessage": "候補は正常に適用され、今後のすべてのクエリー\"{query}\"の候補は自動的に適用されます。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyDisabledMessage": "候補は正常に却下され、クエリー\"{query}\"の候補は今後表示されません。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyRejectedMessage": "候補は正常に却下されました。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.suggestionTitle": "候補", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.title": "候補の管理", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.hideForNowLabel": "これを非表示", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.reviewSuggestionsButtonLabel": "候補のレビュー", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.lastUpdatedTableHeader": "最終更新", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.promotedDocumentsTableHeader": "昇格された結果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.queryTableHeader": "クエリー", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.description": "分析に基づいて、一部のドキュメントを昇格すると、次のクエリーの結果を改善できます。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.overridesLabel": "上書き", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.title": "{totalSuggestions}件の候補", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.viewTooltip": "候補を表示", - "xpack.enterpriseSearch.appSearch.engine.curations.table.automatedLabel": "自動", - "xpack.enterpriseSearch.appSearch.engine.curations.table.column.lastUpdated": "最終更新", - "xpack.enterpriseSearch.appSearch.engine.curations.table.column.queries": "クエリー", - "xpack.enterpriseSearch.appSearch.engine.curations.table.deleteTooltip": "キュレーションを削除", - "xpack.enterpriseSearch.appSearch.engine.curations.table.editTooltip": "キュレーションを編集", - "xpack.enterpriseSearch.appSearch.engine.curations.table.newSuggestionLabel": "新しい候補", - "xpack.enterpriseSearch.appSearch.engine.curations.table.title": "アクティブなキュレーション", - "xpack.enterpriseSearch.appSearch.engine.curations.title": "キュレーション", - "xpack.enterpriseSearch.appSearch.engine.documents.empty.buttonLabel": "ドキュメントガイドを読む", - "xpack.enterpriseSearch.appSearch.engine.elasticsearchEngineBadge": "ELASTICSEARCHインデックス", - "xpack.enterpriseSearch.appSearch.engine.metaEngineBadge": "メタエンジン", - "xpack.enterpriseSearch.appSearch.engine.notFound": "名前''{engineName}''のエンジンが見つかりませんでした。", - "xpack.enterpriseSearch.appSearch.engine.overview.analyticsLink": "分析を表示", - "xpack.enterpriseSearch.appSearch.engine.overview.apiLogsLink": "API ログを表示", - "xpack.enterpriseSearch.appSearch.engine.overview.chartDuration": "過去 7 日間", - "xpack.enterpriseSearch.appSearch.engine.overview.empty.heading": "エンジン設定", - "xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction": "ドキュメンテーションを表示", - "xpack.enterpriseSearch.appSearch.engine.overview.heading": "エンジン概要", - "xpack.enterpriseSearch.appSearch.engine.overview.title": "概要", - "xpack.enterpriseSearch.appSearch.engine.pollingErrorDescription": "接続を確認するか、手動でページを読み込んでください。", - "xpack.enterpriseSearch.appSearch.engine.pollingErrorMessage": "エンジンデータを取得できませんでした", - "xpack.enterpriseSearch.appSearch.engine.queryTester.searchPlaceholder": "検索エンジンドキュメント", - "xpack.enterpriseSearch.appSearch.engine.queryTesterTitle": "クエリーテスト", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addBoostDropDownOptionLabel": "ブーストを追加", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addOperationDropDownOptionLabel": "追加", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.deleteBoostButtonLabel": "ブーストを削除", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.exponentialFunctionDropDownOptionLabel": "指数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.functionalDropDownOptionLabel": "関数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.funtional.functionDropDownLabel": "関数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.funtional.operationDropDownLabel": "演算", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.gaussianFunctionDropDownOptionLabel": "ガウス", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.impactLabel": "インパクト", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.linearFunctionDropDownOptionLabel": "線形", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.logarithmicBoostFunctionDropDownOptionLabel": "対数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.multiplyOperationDropDownOptionLabel": "乗算", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximity.centerLabel": "中央", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximity.functionDropDownLabel": "関数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximityDropDownOptionLabel": "近接", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.title": "ブースト", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.valueDropDownOptionLabel": "値", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description": "エンジンの精度および関連性設定を管理", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFields.title": "無効なフィールド ", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFieldsExplanationMessage": "フィールド型の競合のため無効です", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.elasticsearch.description": "エンジンの精度および関連性設定を管理", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel": "関連するチューニングガイドをお読みください", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.description": "一部のドキュメントにインデックスを作成すると、スキーマが自動的に作成されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.title": "関連性を調整するドキュメントを追加", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoosts": "無効なブースト", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsBannerLabel": "無効なブーストです。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsErrorMessage": "1つ以上のブーストが有効ではありません。おそらくスキーマ型の変更が原因です。古いブーストまたは無効なブーストを削除して、このアラートを消去します。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder": "{schemaFieldsLength}フィールドをフィルタリング...", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.descriptionLabel": "このフィールドを検索", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.rowLabel": "テキスト検索", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.warningLabel": "検索はテキストフィールドでのみ有効にできます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title": "フィールドを管理", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.weight.label": "重み", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteConfirmation": "このブーストを削除しますか?", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteSuccess": "関連性はデフォルト値にリセットされました", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.resetConfirmation": "関連性のデフォルトを復元しますか?", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.successDescription": "変更はすぐに結果に影響します。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.updateSuccess": "関連性が調整されました", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.ariaLabel": "再現率と精度", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.description": "エンジンで精度と再現率設定を微調整します。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link": "詳細情報", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.precision.label": "精度", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.recall.label": "再現率", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step01.description": "再現率を最大にして、精度を最小にする設定。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step02.description": "デフォルト:用語の半分未満が一致する必要があります。完全な誤字許容が適用されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step03.description": "厳しい用語の要件:一致するには、用語が2つ以下の場合は、クエリーのすべての用語がドキュメントに含まれている必要があります。それよりも用語が多い場合は、半分の用語が含まれている必要があります。完全な誤字許容が適用されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step04.description": "厳しい用語の要件:一致するには、用語が3つ以下の場合は、クエリーのすべての用語がドキュメントに含まれている必要があります。それよりも用語が多い場合は、3/4の用語が含まれている必要があります。完全な誤字許容が適用されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step05.description": "厳しい用語の要件:一致するには、用語が4つ以下の場合は、クエリーのすべての用語がドキュメントに含まれている必要があります。それよりも用語が多い場合は、1つを除くすべての用語が含まれている必要があります。完全な誤字許容が適用されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step06.description": "厳しい用語の要件:一致するには、すべてのクエリーのすべての用語がドキュメントに含まれている必要があります。完全な誤字許容が適用されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step07.description": "最も厳しい用語の要件:一致するには、同じフィールドのすべての用語がドキュメントに含まれている必要があります。完全な誤字許容が適用されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step08.description": "最も厳しい用語の要件:一致するには、同じフィールドのすべての用語がドキュメントに含まれている必要があります。部分的な誤字許容が適用されます。あいまい一致は無効です。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step09.description": "最も厳しい用語の要件:一致するには、同じフィールドのすべての用語がドキュメントに含まれている必要があります。部分的な誤字許容が適用されます。あいまい一致とプレフィックスは無効です。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step10.description": "最も厳しい用語の要件:一致するには、同じフィールドのすべての用語がドキュメントに含まれている必要があります。部分的な誤字許容が適用されます。上記のほかに、縮約とハイフネーションは修正されません。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step11.description": "完全一致のみが適用されます。大文字と小文字の差異のみが許容されます。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.title": "精度の調整", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.enterQueryMessage": "検索結果を表示するにはクエリーを入力します", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.noResultsMessage": "一致するコンテンツが見つかりません", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.searchPlaceholder": "{engineName}を検索", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title": "プレビュー", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaConflictsBannerLabel": "無効なフィールド ", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaConflictsErrorMessage": "フィールド型の競合が原因で無効な{schemaFieldsWithConflictsCount, number} {schemaFieldsWithConflictsCount, plural, other {個のフィールド}}。{link}", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaFieldsLinkLabel": "スキーマフィールド", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title": "関連性の調整", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.uncofirmedFieldsBannerLabel": "最近追加されたフィールドはデフォルトで検索されません", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.uncofirmedFieldsErrorMessage": "これらの新しいフィールドを検索可能にする場合は、テキスト検索のトグルを切り替えてオンにします。そうでない場合は、新しい{schemaLink}を確認して、このアラートを消去します。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.unsearchedFields": "検索されていないフィールド", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.whatsThisLinkLabel": "概要", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.clearButtonLabel": "すべての値を消去", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmResetMessage": "結果設定のデフォルトを復元しますか?制限なく、すべてのフィールドが元の状態に設定されます。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmSaveMessage": "変更は直ちに開始します。アプリケーションが新しい検索結果を許可できることを確認してください。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.buttonLabel": "結果設定ガイドを読む", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.description": "一部のドキュメントにインデックスを作成すると、スキーマが自動的に作成されます。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.title": "設定を調整するドキュメントを追加", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.fieldTypeConflictText": "フィールドタイプの矛盾", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.numberFieldPlaceholder": "制限なし", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.pageDescription": "検索結果を充実させ、表示するフィールドを選択します。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.delayedValue": "遅延", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.goodValue": "優れている", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.optimalValue": "最適", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.standardValue": "標準", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformanceLabel": "クエリーパフォーマンス:{performanceValue}", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.errorMessage": "エラーが発生しました。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.inputPlaceholder": "応答をテストするには検索クエリーを入力します...", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.noResultsMessage": "結果がありません。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponseTitle": "サンプル応答", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage": "結果設定が保存されました", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.disabledFieldsTitle": "無効なフィールド ", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.fallbackTitle": "フォールバック", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle": "最大サイズ", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.nonTextFieldsTitle": "非テキストフィールド", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle": "未加工", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.snippetTitle": "スニペット", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.textFieldsTitle": "テキストフィールド", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTitle": "ハイライト", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTooltip": "スニペットはフィールド値のエスケープされた表示です。クエリーの一致はハイライトするためにタグでカプセル化されています。フォールバックはスニペット一致を検索しますが、何も見つからない場合は、エスケープされた元の値にフォールバックします。範囲は20~1000です。デフォルトは100です。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawAriaLabel": "未加工フィールドを切り替える", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTitle": "未加工", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTooltip": "未加工フィールドはフィールド値を正確に表示しています。20文字以上使用してください。デフォルトはフィールド全体です。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetAriaLabel": "テキストスニペットを切り替え", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetFallbackAriaLabel": "スニペットフォールバックを切り替え", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.title": "結果設定", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.unsavedChangesMessage": "結果設定は保存されていません。終了してよろしいですか?", - "xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge": "サンプルエンジン", - "xpack.enterpriseSearch.appSearch.engine.schema.addSchemaErrorMessage": "フィールド名はすでに存在します:{fieldName}", - "xpack.enterpriseSearch.appSearch.engine.schema.addSchemaSuccessMessage": "新しいフィールドが追加されました:{fieldName}", - "xpack.enterpriseSearch.appSearch.engine.schema.confirmSchemaButtonLabel": "タイプの確認", - "xpack.enterpriseSearch.appSearch.engine.schema.conflicts": "スキーマ競合", - "xpack.enterpriseSearch.appSearch.engine.schema.createSchemaFieldButtonLabel": "スキーマフィールドを作成", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel": "インデックススキーマガイドを読む", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.description": "事前にスキーマフィールドを作成するか、一部のドキュメントをインデックスして、スキーマが作成されるようにします。", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.title": "スキーマを作成", - "xpack.enterpriseSearch.appSearch.engine.schema.errors": "スキーマ変更エラー", - "xpack.enterpriseSearch.appSearch.engine.schema.hasIncompleteFields": "すべてのフィールドで精度調整が有効ではありません", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.description": "一部のフィールドで、App Searchで使用される1つ以上のサブフィールドが欠落しています。これらのサブフィールドが追加されるまで、一部の検索機能が動作しない場合があります。", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.link": "詳細情報", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.title": "{count, plural, other {# フィールド}}にはサブフィールドがありません", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsDescription": "1つ以上のエンジンに属するフィールド。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsTitle": "アクティブなフィールド", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.allEngines": "すべて", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutDescription": "フィールドのフィールド型が、このメタエンジンを構成するソースエンジン全体で一致していません。このフィールドを検索可能にするには、ソースエンジンから一貫性のあるフィールド型を適用します。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutTitle": "{conflictingFieldsCount, plural, other {# 個のフィールド}}が検索可能ではありません", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.description": "エンジン別のアクティブなフィールドと非アクティブなフィールド。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.fieldTypeConflicts": "フィールド型の競合", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsDescription": "これらのフィールドの型が競合しています。これらのフィールドを有効にするには、一致するソースエンジンで型を変更します。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsTitle": "非アクティブなフィールド", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.title": "メタエンジンスキーマ", - "xpack.enterpriseSearch.appSearch.engine.schema.missingSubfieldsLabel": "欠落しているサブフィールド", - "xpack.enterpriseSearch.appSearch.engine.schema.pageDescription": "新しいフィールドを追加するか、既存のフィールドの型を変更します。", - "xpack.enterpriseSearch.appSearch.engine.schema.pageTitle": "メタエンジンスキーマを管理", - "xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageDescription": "スキーマフィールド型を表示します。", - "xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageTitle": "エンジンスキーマ", - "xpack.enterpriseSearch.appSearch.engine.schema.reindexErrorsBreadcrumb": "再インデックスエラー", - "xpack.enterpriseSearch.appSearch.engine.schema.reindexJob.title": "スキーマ変更エラー", - "xpack.enterpriseSearch.appSearch.engine.schema.title": "スキーマ", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFieldLabel": "最近追加された項目", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields": "新しい未確認のフィールド", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.description": "新しいスキーマフィールドを正しい型または想定される型に設定してから、フィールド型を確認します。", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.title": "最近新しいスキーマフィールドが追加されました", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.description": "これらの新しいフィールドを検索可能にするには、検索設定を更新してこれらのフィールドを追加してください。検索不可能にする場合は、新しいフィールド型を確認してこのアラートを消去してください。", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.searchSettingsButtonLabel": "検索設定を更新", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.title": "最近追加されたフィールドはデフォルトで検索されません", - "xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaButtonLabel": "変更を保存", - "xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaSuccessMessage": "スキーマが更新されました", - "xpack.enterpriseSearch.appSearch.engine.searchUI.bodyDescription": "Search UIはReactで検索経験を構築するための無料のオープンライブラリです。{link}。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel": "Search UIガイドを読む", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.description": "一部のドキュメントにインデックスを作成すると、スキーマが自動的に作成されます。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.title": "Search UIを生成するドキュメントを追加", - "xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldHelpText": "取得された値はフィルターとして表示され、クエリーの絞り込みで使用できます", - "xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldLabel": "フィールドのフィルタリング(任意)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.generatePreviewButtonLabel": "検索経験を生成", - "xpack.enterpriseSearch.appSearch.engine.searchUI.guideLinkText": "Search UIの詳細を参照してください", - "xpack.enterpriseSearch.appSearch.engine.searchUI.lowerBodyDescription": "下のフィールドを使用して、Search UIで構築されたサンプル検索経験を生成します。サンプルを使用して検索結果をプレビューするか、サンプルに基づいて独自のカスタム検索経験を作成します。{link}。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.noSearchKeyErrorMessage": "''{engineName}''エンジンへのアクセス権があるパブリック検索キーがない可能性があります。設定するには、{credentialsTitle}ページを開いてください。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.repositoryLinkText": "Github repoを表示", - "xpack.enterpriseSearch.appSearch.engine.searchUI.sortFieldLabel": "フィールドの並べ替え(任意)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.sortHelpText": "結果の並べ替えオプション(昇順と降順)を表示するために使用されます", - "xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldHelpText": "サムネイル画像を表示する画像URLを指定", - "xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldLabel": "サムネイルフィールド(任意)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.title": "Search UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldHelpText": "すべてのレンダリングされた結果の最上位の視覚的IDとして使用されます", - "xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldLabel": "タイトルフィールド(任意)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldHelpText": "該当する場合は、結果のリンク先として使用されます", - "xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldLabel": "URLフィールド(任意)", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesButtonLabel": "エンジンの追加", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.description": "追加のエンジンをこのメタエンジンに追加します。", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.title": "エンジンの追加", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesPlaceholder": "エンジンを選択", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesSuccessMessage": "{sourceEnginesCount, plural, other {# 個のエンジン}}がこのメタエンジンに追加されました", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.removeSourceEngineSuccessMessage": "エンジン''{engineName}''はこのメタエンジンから削除されました", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.title": "エンジンの管理", - "xpack.enterpriseSearch.appSearch.engine.suggestedCurationsCallout.description": "エンジンの分析に基づいて、レビュー可能な新しい提案されたキュレーションがあります。", - "xpack.enterpriseSearch.appSearch.engine.suggestedCurationsCallout.title": "レビューする新しい提案されたキュレーション", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSuccessMessage": "同義語セットが作成されました", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetButtonLabel": "同義語セットを作成", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetTitle": "同義語セットを追加", - "xpack.enterpriseSearch.appSearch.engine.synonyms.deleteConfirmationMessage": "この同義語セットを削除しますか?", - "xpack.enterpriseSearch.appSearch.engine.synonyms.deleteSuccessMessage": "同義語セットが削除されました", - "xpack.enterpriseSearch.appSearch.engine.synonyms.description": "同義語を使用して、データセットで文脈的に同じ意味を有するクエリーを関連付けます。", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel": "同義語ガイドを読む", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.description": "同義語はクエリーを同じ文脈または意味と関連付けます。これらを使用して、ユーザーを関連するコンテンツに案内します。", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.title": "最初の同義語セットを作成", - "xpack.enterpriseSearch.appSearch.engine.synonyms.iconAriaLabel": "同義語", - "xpack.enterpriseSearch.appSearch.engine.synonyms.impactDescription": "このセットはすぐに結果に影響します。", - "xpack.enterpriseSearch.appSearch.engine.synonyms.synonymInputPlaceholder": "同義語を入力", - "xpack.enterpriseSearch.appSearch.engine.synonyms.title": "同義語", - "xpack.enterpriseSearch.appSearch.engine.synonyms.updateSuccessMessage": "同義語セットが更新されました", - "xpack.enterpriseSearch.appSearch.engine.synonyms.updateSynonymSetTitle": "同義語セットを管理", - "xpack.enterpriseSearch.appSearch.engine.universalLanguage": "ユニバーサル", - "xpack.enterpriseSearch.appSearch.engineAssignmentLabel": "エンジン割り当て", - "xpack.enterpriseSearch.appSearch.engineCreation.configureElasticsearchEngine.callout.title": "エイリアスが作成され、このエンジンで使用されます", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.errorText": "名前{aliasName}の既存のインデックスまたはエイリアスが存在します。別のエイリアス名を選択してください。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.label": "エイリアス名", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefix.helpText": "App Searchエンジンで使用するには、エイリアス名に「search-」プレフィックスが必要です", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefixAndNamed.helpText": "App Searchエンジンで使用するには、エイリアス名に「search-」プレフィックスが必要です。エイリアスの名前が指定されます", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.appSearch.description": "App Searchエンジンの一意の名前と任意の言語選択を入力します。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.backButton.label": "戻る", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.body": "App Searchエンジンは、プレフィックス「search-」のインデックスまたはエイリアスでのみ作成できます。先頭が「search-」ではないインデックスを選択した場合、そのインデックスのエイリアスが作成され、使用されます。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.title": "App Searchには、インデックスおよびエイリアス名の要件があります", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.continue.label": "続行", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.description": "App Searchエンジンの一意の名前を入力し、インデックスを選択します。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.docCount": "ドキュメント数", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.indexSelectorAriaLabel": "使用するElasticsearchインデックスを選択します", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.selectable.empty": "Elasticsearchインデックスがありません", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.selectable.loading": "Elasticsearchインデックスを読み込んでいます", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.status": "ステータス", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.storage": "ストレージサイズ", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.searchIndexSelectable.helpText": "「search-」プレフィックスのインデックスまたはエイリアスを選択するか、以下で新しいエイリアスを作成します", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.searchIndexSelectable.label": "使用するElasticsearchインデックスを選択します", - "xpack.enterpriseSearch.appSearch.engineCreation.form.backButton.label": "戻る", - "xpack.enterpriseSearch.appSearch.engineCreation.form.continue.label": "検索エンジンの作成", - "xpack.enterpriseSearch.appSearch.engineCreation.form.editConfiguration.label": "構成の編集", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineLanguage.label": "エンジン言語", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.allowedCharactersHelpText": "エンジン名には、小文字、数字、ハイフンのみを使用できます。", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.label": "エンジン名", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.placeholder": "例:my-search-engine", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.sanitizedNameHelpText": "エンジン名が変更されます", - "xpack.enterpriseSearch.appSearch.engineCreation.form.submitButton.buttonLabel": "エンジンを作成", - "xpack.enterpriseSearch.appSearch.engineCreation.form.title": "検索エンジンを構成", - "xpack.enterpriseSearch.appSearch.engineCreation.nextStep.buttonLabel": "続行", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.aliasName.title": "エイリアス名", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.description": "App Searchエンジンは、次の構成で作成されます。", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.elasticsearchIndex.title": "Elasticsearchインデックス", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineName.title": "エンジン名", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.description": "Elasticsearchインデックスベース", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.title": "エンジンタイプ", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.title.label": "検索エンジンを確認", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.appSearch.description": "App Search APIを使用して、ドキュメントを管理します。App Searchはドキュメントを基本のインデックスに書き込み、管理します。", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.appSearch.title": "App Searchで管理されたドキュメント", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.elasticsearch.description": "既存のインデックスを使用して、ドキュメントを管理します。検索とApp SearchをElasticsearchインデックスに追加します。一部の関数には、特定のサブフィールドが必要です。", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.elasticsearch.title": "Elasticsearchインデックスベース", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description": "既存のElasticsearchインデックスを使用する検索エンジンを作成できるようになりました。これにより、App Searchの検索管理ツールとElasticsearchインデックスの柔軟性を組み合わせることができます。", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description.link": "詳細", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.title": "検索エンジンタイプを選択", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label": "構成", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.finish.label": "終了", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.review.label": "見直し", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label": "検索エンジンタイプ", - "xpack.enterpriseSearch.appSearch.engineCreation.successMessage": "エンジン''{name}''が作成されました", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.chineseDropDownOptionLabel": "中国語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.danishDropDownOptionLabel": "デンマーク語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.dutchDropDownOptionLabel": "オランダ語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.englishDropDownOptionLabel": "英語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.frenchDropDownOptionLabel": "フランス語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.germanDropDownOptionLabel": "ドイツ語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.italianDropDownOptionLabel": "イタリア語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.japaneseDropDownOptionLabel": "日本語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.koreanDropDownOptionLabel": "韓国語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseBrazilDropDownOptionLabel": "ポルトガル語(ブラジル)", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseDropDownOptionLabel": "ポルトガル語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.russianDropDownOptionLabel": "ロシア語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.spanishDropDownOptionLabel": "スペイン語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.thaiDropDownOptionLabel": "タイ語", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.universalDropDownOptionLabel": "ユニバーサル", - "xpack.enterpriseSearch.appSearch.engineCreation.title": "エンジンを作成", - "xpack.enterpriseSearch.appSearch.engineRequiredError": "1つ以上の割り当てられたエンジンが必要です。", - "xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsButtonLabel": "更新", - "xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsMessage": "新しいイベントが記録されました。", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.closeButton": "閉じる", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.eventTip": "過去24時間のイベントを表示しています", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.eventCategory": "イベントカテゴリ", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.eventType": "イベントタイプ", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.outcome": "成果", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.updatedBy": "更新者", - "xpack.enterpriseSearch.appSearch.engines.createEngineButtonLabel": "エンジンを作成", - "xpack.enterpriseSearch.appSearch.engines.createMetaEngineButtonLabel": "メタエンジンを作成", - "xpack.enterpriseSearch.appSearch.engines.curations.refreshButton": "更新", - "xpack.enterpriseSearch.appSearch.engines.curations.rejectedCurationsHistoryPanel.refresh": "更新", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptButtonLabel": "メタエンジンの詳細", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptDescription": "メタエンジンでは、複数のエンジンを1つの検索可能なエンジンに統合できます。", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptTitle": "最初のメタエンジンを作成", - "xpack.enterpriseSearch.appSearch.engines.metaEnginesTable.fieldTypeConflictWarning": "フィールドタイプの矛盾", - "xpack.enterpriseSearch.appSearch.engines.metaEnginesTable.sourceEnginesCount": "{sourceEnginesCount, plural, other {# エンジン}}", - "xpack.enterpriseSearch.appSearch.engines.title": "エンジン", - "xpack.enterpriseSearch.appSearch.enginesOverview.metaEnginesTable.sourceEngines.title": "ソースエンジン", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.buttonDescription": "このエンジンを削除", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.confirmationPopupMessage": " \"{engineName}\"とすべての内容を完全に削除しますか?", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.successMessage": "エンジン''{engineName}''が削除されました", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage.buttonDescription": "このエンジンを管理", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions": "アクション", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt": "作成日時:", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount": "ドキュメントカウント", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount": "フィールドカウント", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.language": "言語", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.lastUpdated": "最終更新", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name": "名前", - "xpack.enterpriseSearch.appSearch.enginesOverview.title": "エンジン概要", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.description": "フィードバックを送信することにより、お客様は当社の{termsOfService}を読んで同意したこと、およびお客様が上記で提供した詳細情報を使用して当社の関連製品およびサービスについてElasticが{contact}する可能性があることを承諾します。詳細については、{privacyStatementLink}をご覧ください。", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.Label": "その他のご意見・ご感想をお聞かせください。", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.optionalLabel": "オプション", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureButtonLabel": "検索分析を追加", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureDescription": "検索アプリケーションの分析とログを追加して表示", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureName": "検索分析とログ", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.panelText": "Behavioral Analyticsを使うと、ユーザーの検索やクリックの行動を追跡して分析できます。Webサイトやアプリケーションをインストルメントし、関連するユーザーのアクションを追跡します。", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureButtonLabel": "Elasticsearchで保護", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureDescription": "検索エンドポイントへのアクセスに必要なユーザー、ロール、資格情報を管理", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureName": "資格情報とロール", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.panelText": "Elasticsearchは、ドキュメントレベルのセキュリティやロールベースのアクセス制御など、包括的なセキュリティ機能を提供しています。", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureButtonLabel": "クエリルールを使用", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureDescription": "特定のクエリに対する検索結果を整理して固定", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureName": "結果を整理", - "xpack.enterpriseSearch.appSearch.gateForm.curations.panelText": "クエリルールは、特定の条件やメタデータに一致するクエリに対して、検索結果をカスタマイズするためのより強力なツールセットを提供します。", - "xpack.enterpriseSearch.appSearch.gateForm.description": "スタンドアロンのApp Search製品は引き続きメンテナンスモードでご利用いただけますが、新しい検索エクスペリエンスにはお勧めできません。ネイティブのElasticsearchツールの使用をお勧めします。このツールは柔軟性と拡張性を備え、魅力的な新しい検索機能も含まれています。お客様のユースケースに最適なツールを選択できるように、当社はこの推奨ウィザードを作成しました。必要な機能を選択すると、対応するElasticsearchの機能をご案内します。スタンドアロンのApp Search製品をご利用になりたい場合は、フォームを送信した後でご利用いただけます。", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.learnMore": "詳細", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.subTitle": "あなたの選択に基づくお勧め:", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.title": "Elasticsearchネイティブに相当", - "xpack.enterpriseSearch.appSearch.gateForm.featureOther.Label": "他にどのような機能をお探しですか。", - "xpack.enterpriseSearch.appSearch.gateForm.features.Label": "どのようなApp Search機能をお探しですか。", - "xpack.enterpriseSearch.appSearch.gateForm.features.selectOption": "オプションを選択", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label": "Elasticsearchを改善するためのユーザー調査にご参加ください。", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.No": "いいえ", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.Yes": "はい", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.optionalLabel": "オプション", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureButtonLabel": "検索の関連性を微調整", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureDescription": "ランキングとブーストの方法を使用して、検索キーワードの関連性を調整", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureName": "関連性の調整", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.panelText": "ElasticsearchのクエリDSLは、包括的な関連性ツールセットを提供します。", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureButtonLabel": "Search UIで検索エクスペリエンスを構築", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureDescription": "検索アプリケーションのユーザーエクスペリエンスを管理するために、グラフィカルユーザーインターフェース(GUI)を使用", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureName": "検索および管理UI", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.panelText": "Search UIは、最新の検索エクスペリエンスを構築するために必要なコンポーネントを提供します。", - "xpack.enterpriseSearch.appSearch.gateForm.submit": "送信", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureButtonLabel": "類義語で検索", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureDescription": "類義語に基づくクエリ拡張で検索を実行", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureName": "類義語で検索", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.panelText": "Elasticsearch Synonyms APIを使用すると、類義語セットを簡単に作成および管理できます。", - "xpack.enterpriseSearch.appSearch.gateForm.title": "はじめに...", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureButtonLabel": "オープンクローラーを試す", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureDescription": "Webクローラーを使用して、WebコンテンツをElasticsearchにインジェスト", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureName": "Webクローラー", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.panelText": "新しいセルフマネージドElasticオープンクローラーが利用可能になったことをご存知ですか?Webコンテンツを、検索に最適化されたインデックスと同期させることができます。", - "xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsDetail": "分析とログを管理するには、{visitSettingsLink}してください。", - "xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsLinkText": "設定を表示", - "xpack.enterpriseSearch.appSearch.logRetention.callout.disabledSinceTitle": "{logsTitle}は、{disabledDate}以降に無効にされました。", - "xpack.enterpriseSearch.appSearch.logRetention.callout.disabledTitle": "{logsTitle}は無効です。", - "xpack.enterpriseSearch.appSearch.logRetention.customPolicy": "カスタム{logsType}ログ保持ポリシーがあります。", - "xpack.enterpriseSearch.appSearch.logRetention.defaultPolicy": "{logsType}ログは{minAgeDays, plural, other {# 日}}以上保存されています。", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging": "すべてのエンジンの{logsType}ログが無効です。", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging.collected": "前回の{logsType}ログは{disabledAtDate}に収集されました。", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging.notCollected": "収集された{logsType}ログはありません。", - "xpack.enterpriseSearch.appSearch.logRetention.tooltip": "ログ保持情報", - "xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.capitalized": "分析", - "xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.lowercase": "分析", - "xpack.enterpriseSearch.appSearch.logRetention.type.api.title.capitalized": "API", - "xpack.enterpriseSearch.appSearch.logRetention.type.api.title.lowercase": "API", - "xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.capitalized": "監査", - "xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.lowercase": "監査", - "xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.capitalized": "Webクローラー", - "xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.lowercase": "Webクローラー", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationDescription": "基本操作については、{documentationLink}。", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationLink": "ドキュメントを読む", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.allowedCharactersHelpText": "メタエンジン名には、小文字、数字、ハイフンのみを使用できます。", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.label": "メタエンジン名", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.placeholder": "例:my-meta-engine", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.sanitizedNameHelpText": "メタエンジン名が設定されます", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.metaEngineDescription": "メタエンジンでは、複数のエンジンを1つの検索可能なエンジンに統合できます。", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.label": "ソースエンジンをこのメタエンジンに追加", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.maxSourceEnginesWarningTitle": "メタエンジンのソースエンジンの上限は{maxEnginesPerMetaEngine}です", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.submitButton.buttonLabel": "メタエンジンを作成", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.title": "メタエンジン名を指定", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.successMessage": "メタエンジン''{name}''が作成されました", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.title": "メタエンジンを作成", - "xpack.enterpriseSearch.appSearch.metaEngines.title": "メタエンジン", - "xpack.enterpriseSearch.appSearch.multiInputRows.addValueButtonLabel": "値を追加", - "xpack.enterpriseSearch.appSearch.multiInputRows.inputRowPlaceholder": "値を入力", - "xpack.enterpriseSearch.appSearch.ownerRoleTypeDescription": "所有者はすべての操作を実行できます。アカウントには複数の所有者がいる場合がありますが、一度に少なくとも1人以上の所有者が必要です。", - "xpack.enterpriseSearch.appSearch.productCardDescription": "アプリやWebサイト向けのカスタムソリューション。ユーザー向けの検索エクスペリエンスを設計、実装、効果的に管理するために必要なツールを提供します。", - "xpack.enterpriseSearch.appSearch.productDescription": "ダッシュボード、分析、APIを活用し、高度なアプリケーション検索をシンプルにします。", - "xpack.enterpriseSearch.appSearch.productName": "App Search", - "xpack.enterpriseSearch.appSearch.result.clicks": "{clicks}クリック", - "xpack.enterpriseSearch.appSearch.result.documentDetailLink": "ドキュメントの詳細を表示", - "xpack.enterpriseSearch.appSearch.result.hideAdditionalFields": "追加フィールドを非表示", - "xpack.enterpriseSearch.appSearch.result.resultPositionLabel": "#{resultPosition}", - "xpack.enterpriseSearch.appSearch.result.showAdditionalFields": "{numberOfAdditionalFields, number}個の追加の {numberOfAdditionalFields, plural, other {フィールド}}を表示。", - "xpack.enterpriseSearch.appSearch.result.title": "ドキュメント{id}", - "xpack.enterpriseSearch.appSearch.roleMappingCreatedMessage": "ロールマッピングが作成されました", - "xpack.enterpriseSearch.appSearch.roleMappingDeletedMessage": "ロールマッピングが削除されました", - "xpack.enterpriseSearch.appSearch.roleMappingsEngineAccessHeading": "エンジンアクセス", - "xpack.enterpriseSearch.appSearch.roleMappingUpdatedMessage": "ロールマッピングが更新されました", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.buttonLabel": "サンプルエンジンを試す", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.description": "サンプルデータでエンジンをテストします。", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.title": "ティアを始めたばかりの場合", - "xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label": "ログ分析イベント", - "xpack.enterpriseSearch.appSearch.settings.logRetention.api.label": "ログAPIイベント", - "xpack.enterpriseSearch.appSearch.settings.logRetention.audit.label": "監査イベントのログ出力", - "xpack.enterpriseSearch.appSearch.settings.logRetention.crawler.label": "Webクローラーログ", - "xpack.enterpriseSearch.appSearch.settings.logRetention.description": "ログ保持はデプロイのILMポリシーで決定されます。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore": "エンタープライズ サーチのログ保持の詳細をご覧ください。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.description": "書き込みを無効にすると、エンジンが分析イベントのログを停止します。既存のデータは保存時間フレームに従って削除されます。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.subheading": "分析ログは現在 {minAgeDays} 日間保存されています。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.title": "分析書き込みを無効にする", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.description": "書き込みを無効にすると、エンジンがAPIイベントのログを停止します。既存のデータは保存時間フレームに従って削除されます。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.subheading": "API ログは現在 {minAgeDays} 日間保存されています。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.title": "API 書き込みを無効にする", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.description": "書き込みを無効にすると、エンジンが監査イベントのログを停止します。既存のデータは保存時間フレームに従って削除されます。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.subheading": "監査ログは現在{minAgeDays}日間保存されています。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.title": "監査の書き込みを無効にする", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.description": "書き込みを無効にすると、エンジンがWebクローラーイベントのログを停止します。既存のデータは保存時間フレームに従って削除されます。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.subheading": "現在、Webクローラーログは{minAgeDays}日間保存されています。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.title": "Webクローラーの書き込みを無効にする", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.disable": "無効にする", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.prompt": "確認する「{target}」を入力します。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.recovery": "削除されたデータは復元できません。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.save": "設定を保存", - "xpack.enterpriseSearch.appSearch.settings.logRetention.title": "ログ保持", - "xpack.enterpriseSearch.appSearch.settings.title": "設定", - "xpack.enterpriseSearch.appSearch.setupGuide.description": "強力な検索を設計し、Webサイトやモバイルアプリケーションにデプロイするためのツールをご利用ください。", - "xpack.enterpriseSearch.appSearch.setupGuide.notConfigured": "App SearchはまだKibanaインスタンスで構成されていません。", - "xpack.enterpriseSearch.appSearch.setupGuide.videoAlt": "App Searchの基本という短い動画では、App Searchを起動して実行する方法について説明します。", - "xpack.enterpriseSearch.appSearch.sourceEngines.removeEngineButton.label": "メタエンジンから削除", - "xpack.enterpriseSearch.appSearch.specificEnginesDescription": "選択したエンジンのセットに静的に割り当てます。", - "xpack.enterpriseSearch.appSearch.specificEnginesLabel": "特定のエンジンに割り当て", - "xpack.enterpriseSearch.appSearch.tokens.admin.description": "資格情報APIとの連携では、非公開管理キーが使用されます。", - "xpack.enterpriseSearch.appSearch.tokens.admin.name": "非公開管理キー", - "xpack.enterpriseSearch.appSearch.tokens.created": "APIキー''{name}''が作成されました", - "xpack.enterpriseSearch.appSearch.tokens.deleted": "APIキー''{name}''が削除されました", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.all": "すべて", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.readonly": "読み取り専用", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.readwrite": "読み取り/書き込み", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.search": "検索", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.writeonly": "書き込み専用", - "xpack.enterpriseSearch.appSearch.tokens.private.description": "1 つ以上のエンジンに対する読み取り/書き込みアクセス権を得るために、非公開 API キーが使用されます。", - "xpack.enterpriseSearch.appSearch.tokens.private.name": "非公開APIキー", - "xpack.enterpriseSearch.appSearch.tokens.search.description": "エンドポイントのみの検索では、公開検索キーが使用されます。", - "xpack.enterpriseSearch.appSearch.tokens.search.name": "公開検索キー", - "xpack.enterpriseSearch.appSearch.tokens.update": "APIキー''{name}''が更新されました", "xpack.enterpriseSearch.attachIndexBox.createSameIndexButtonLabel": "インデックス{indexName}を作成して付ける", "xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexErrorTextLabel": "既存のインデックス名を使用して、新しいインデックスを作成できません。既存のインデックスを選択するか、新しい名前で新しいインデックスを作成できます。", "xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexHelpTextLabel": "既存のインデックスを使用するか、新しく作成できます。", @@ -17888,7 +16975,6 @@ "xpack.enterpriseSearch.crawler.startCrawlContextMenu.reapplyCrawlRulesMenuLabel": "クローリングルールを再適用", "xpack.enterpriseSearch.crawler.urlComboBox.invalidUrlErrorMessage": "有効なURLを入力してください", "xpack.enterpriseSearch.crawlerEmptyState.h2.createYourFirstWebLabel": "最初のWebクローラーを作成", - "xpack.enterpriseSearch.crawlerEmptyState.newWebCrawlerButtonLabel": "新しいWebクローラー", "xpack.enterpriseSearch.crawlerEmptyState.openSourceCrawlerButtonLabel": "ソースコード", "xpack.enterpriseSearch.crawlerEmptyState.p.discoverExtractAndIndexLabel": "Webサイトやナレッジベースから検索可能なコンテンツを検出、抽出、インデックス化します", "xpack.enterpriseSearch.crawlers.title": "Elasticsearch Webクローラー", @@ -17948,8 +17034,6 @@ "xpack.enterpriseSearch.createConnector.startStep.p.whereDoYouWantLabel": "コネクターを保存する場所と管理する方法", "xpack.enterpriseSearch.createConnector.startStep.p.youWillStartTheLabel": "新しいインデックス、APIキー、WebクローラーコネクターIDの作成プロセスを手動で開始します。任意で、独自の構成を使用することもできます。", "xpack.enterpriseSearch.createConnector.startStep.startLabel": "開始", - "xpack.enterpriseSearch.curations.settings.licenseUpgradeLink": "ライセンスアップグレードの詳細", - "xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel": "30 日間のトライアルの開始", "xpack.enterpriseSearch.defaultSettingsFlyout.body.description.ingestPipelinesLink.link": "インジェストパイプライン", "xpack.enterpriseSearch.defaultSettingsFlyout.body.description.label": "これらの設定は、Searchインジェストメカニズムで作成されたすべての新しいElasticsearchインデックスに適用されます。APIインジェストベースのインデックスの場合は、ドキュメントをインジェストするときに、必ずパイプラインを含めてください。これらの機能は{link}によって実現されます", "xpack.enterpriseSearch.defaultSettingsFlyout.callout.body": "インデックスの構成ページで、特定のインデックスに対して、この機能を有効化または無効化することもできます。", @@ -17972,8 +17056,6 @@ "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "場所を問わず、何でも検索。組織を支える多忙なチームのために、パワフルでモダンな検索エクスペリエンスを簡単に導入できます。Webサイトやアプリ、ワークプレイスに事前調整済みの検索をすばやく追加しましょう。何でもシンプルに検索できます。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "エンタープライズ サーチはまだKibanaインスタンスで構成されていません。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "エンタープライズ サーチの基本操作", - "xpack.enterpriseSearch.enterpriseSearchCard.cta": "詳細", - "xpack.enterpriseSearch.entSearch.productCardDescription": "よりシンプルで使いやすく、ビジネスに特化した検索エクスペリエンスを提供するスタンドアロンアプリケーション。", "xpack.enterpriseSearch.exampleConnectorLabel": "例", "xpack.enterpriseSearch.finishUpStep.euiButton.viewInDiscoverLabel": "Discoverに表示", "xpack.enterpriseSearch.getConnectorTypeBadge.connectorClientBadgeLabel": "セルフマネージド", @@ -18054,8 +17136,6 @@ "xpack.enterpriseSearch.ingestSelector.method.connectorButtonLabel": "コネクターを作成する", "xpack.enterpriseSearch.ingestSelector.method.connectors": "コネクター", "xpack.enterpriseSearch.ingestSelector.method.connectors.description": "サードパーティのデータソースからデータを抽出、変換、インデックス化、同期します。", - "xpack.enterpriseSearch.ingestSelector.method.crawler": "Webクローラー", - "xpack.enterpriseSearch.ingestSelector.method.crawler.description": "Webサイトやナレッジベースから検索可能なコンテンツを検出、抽出、インデックス化します。", "xpack.enterpriseSearch.ingestSelector.method.fileUpload": "ファイルをアップロード", "xpack.enterpriseSearch.ingestSelector.method.fileUpload.description": "CSVやTSV、改行区切りのJSONなどの区切られたテキストファイル。", "xpack.enterpriseSearch.ingestSelector.method.fileUploadLabel": "ファイルを選択", @@ -18064,12 +17144,9 @@ "xpack.enterpriseSearch.ingestSelector.method.languageClients.description": "当社がサポートするすべての言語クライアントと、それらをAPIで使用する方法をご覧ください。", "xpack.enterpriseSearch.ingestSelector.method.sampleData": "サンプルデータ", "xpack.enterpriseSearch.ingestSelector.method.sampleData.description": "すぐに検索できるデータサンプルを使って、検索体験をお試しください。", - "xpack.enterpriseSearch.ingestSelector.method.sourceCodeButtonLabel": "ソースコード", "xpack.enterpriseSearch.inlineEditableTable.newRowButtonLabel": "新しい行", "xpack.enterpriseSearch.integrations.apiDescription": "Elasticsearchの堅牢なAPIを使用して、検索をアプリケーションに追加します。", "xpack.enterpriseSearch.integrations.apiName": "API", - "xpack.enterpriseSearch.integrations.webCrawlerDescription": "Webクローラーを使用して、検索をWebサイトに追加します。", - "xpack.enterpriseSearch.integrations.webCrawlerName": "Webクローラー", "xpack.enterpriseSearch.invalidJsonError": "無効なJSON", "xpack.enterpriseSearch.languages.cURL": "cURL", "xpack.enterpriseSearch.languages.cURL.githubLink": "curl", @@ -18111,7 +17188,6 @@ "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "スケジュール", "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "同期ルール", "xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "検索アプリケーション", - "xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "エンジン", "xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "コネクター", "xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "インデックス", "xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "Webクローラー", @@ -18158,84 +17234,9 @@ "xpack.enterpriseSearch.productSelector.overview.description": "検索エクスペリエンスを構築する最初のステップは、検索に最適化されたElasticsearchインデックスを作成し、そこにコンテンツをインポートすることです。Elasticsearchでは、お客様の技術的な専門知識やデータソースに最適な使いやすいオプションをいくつかご用意しています。", "xpack.enterpriseSearch.productSelector.overview.title": "データをインジェスト", "xpack.enterpriseSearch.productSelectorCalloutTitle": "チームのためのエンタープライズレベルの機能を実現できるようにアップグレード", - "xpack.enterpriseSearch.readOnlyMode.warning": "エンタープライズ サーチは読み取り専用モードです。作成、編集、削除などの変更を実行できません。", "xpack.enterpriseSearch.required": "必須", "xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel": "要件を確認", "xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel": "{serviceType}に接続するための前提条件と構成要件については、このコネクターのドキュメントを参照してください。", - "xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "マッピングを追加", - "xpack.enterpriseSearch.roleMapping.addUserLabel": "ユーザーの追加", - "xpack.enterpriseSearch.roleMapping.allLabel": "すべて", - "xpack.enterpriseSearch.roleMapping.anyAuthProviderLabel": "すべての現在または将来の認証プロバイダー", - "xpack.enterpriseSearch.roleMapping.attributeSelectorTitle": "属性マッピング", - "xpack.enterpriseSearch.roleMapping.attributeValueLabel": "属性値", - "xpack.enterpriseSearch.roleMapping.deactivatedLabel": "無効", - "xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutDescription": "現在、このユーザーは無効です。アクセス権は一時的に取り消されました。Kibanaコンソールの[ユーザー管理]領域からユーザーを再アクティブ化できます。", - "xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutLabel": "ユーザーが無効にされました", - "xpack.enterpriseSearch.roleMapping.deleteRoleMappingDescription": "マッピングの削除は永久的であり、元に戻すことはできません", - "xpack.enterpriseSearch.roleMapping.emailLabel": "メール", - "xpack.enterpriseSearch.roleMapping.enableRolesButton": "ロールベースのアクセスを許可", - "xpack.enterpriseSearch.roleMapping.enableRolesLink": "ロールベースのアクセスの詳細", - "xpack.enterpriseSearch.roleMapping.enableUsersLink": "ユーザー管理の詳細", - "xpack.enterpriseSearch.roleMapping.enginesLabel": "エンジン", - "xpack.enterpriseSearch.roleMapping.existingInvitationLabel": "このユーザーはまだ招待を承諾していません。", - "xpack.enterpriseSearch.roleMapping.existingUserLabel": "既存のユーザーを追加", - "xpack.enterpriseSearch.roleMapping.externalAttributeLabel": "外部属性", - "xpack.enterpriseSearch.roleMapping.externalAttributeTooltip": "外部属性はIDプロバイダーによって定義され、サービスごとに異なります。", - "xpack.enterpriseSearch.roleMapping.filterRoleMappingsPlaceholder": "フィルターロールマッピング", - "xpack.enterpriseSearch.roleMapping.filterUsersLabel": "ユーザーをフィルター", - "xpack.enterpriseSearch.roleMapping.flyoutCreateTitle": "ロールマッピングの作成", - "xpack.enterpriseSearch.roleMapping.flyoutDescription": "ユーザー属性に基づいてロールとアクセス権を割り当てます", - "xpack.enterpriseSearch.roleMapping.flyoutUpdateTitle": "ロールマッピングを更新", - "xpack.enterpriseSearch.roleMapping.groupsLabel": "グループ", - "xpack.enterpriseSearch.roleMapping.individualAuthProviderLabel": "個別の認証プロバイダーを選択", - "xpack.enterpriseSearch.roleMapping.invitationDescription": "このURLをユーザーと共有すると、ユーザーはエンタープライズ サーチの招待を承諾したり、新しいパスワードを設定したりできます", - "xpack.enterpriseSearch.roleMapping.invitationLink": "エンタープライズ サーチの招待リンク", - "xpack.enterpriseSearch.roleMapping.invitationPendingLabel": "招待保留", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningDescription": "「enterprise-search-user」ロールを付与することを検討してください。", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningErrorMessage": "このElasticsearchユーザーはElasticsearchでエンタープライズ サーチロールが割り当てられていません。Kibanaへのアクセス権がない可能性があります。", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningTitle": "Kibanaアクセスの警告", - "xpack.enterpriseSearch.roleMapping.manageRoleMappingTitle": "ロールマッピングを管理", - "xpack.enterpriseSearch.roleMapping.newInvitationLabel": "招待URL", - "xpack.enterpriseSearch.roleMapping.newRoleMappingTitle": "ロールマッピングを追加", - "xpack.enterpriseSearch.roleMapping.newUserDescription": "粒度の高いアクセス権とアクセス許可を提供", - "xpack.enterpriseSearch.roleMapping.newUserLabel": "新規ユーザーを作成", - "xpack.enterpriseSearch.roleMapping.noResults.message": "一致するロールマッピングが見つかりません", - "xpack.enterpriseSearch.roleMapping.notFoundMessage": "一致するロールマッピングが見つかりません。", - "xpack.enterpriseSearch.roleMapping.noUsersDescription": "柔軟にユーザーを個別に追加できます。ロールマッピングは、ユーザー属性を使用して多数のユーザーを追加するための幅広いインターフェースを提供します。", - "xpack.enterpriseSearch.roleMapping.noUsersLabel": "一致するユーザーが見つかりません", - "xpack.enterpriseSearch.roleMapping.noUsersTitle": "ユーザーが追加されません", - "xpack.enterpriseSearch.roleMapping.rbacButtonDisabledLabel": "RBACの有効化はスーパーユーザーが実行できます。", - "xpack.enterpriseSearch.roleMapping.removeRoleMappingButton": "マッピングの削除", - "xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle": "ロールマッピングの削除", - "xpack.enterpriseSearch.roleMapping.removeUserButton": "ユーザーの削除", - "xpack.enterpriseSearch.roleMapping.requiredLabel": "必須", - "xpack.enterpriseSearch.roleMapping.roleLabel": "ロール", - "xpack.enterpriseSearch.roleMapping.roleMappingFlyoutCreateButton": "マッピングを作成", - "xpack.enterpriseSearch.roleMapping.roleMappingFlyoutUpdateButton": "マッピングを更新", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingButton": "新しいロールマッピングの作成", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDescription": "ロールマッピングはネイティブまたはSAMLで統制されたロール属性を{productName}アクセス権に関連付けるためのインターフェースを提供します。", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDocsLink": "ロールマッピングの詳細を参照してください。", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingTitle": "ロールマッピング", - "xpack.enterpriseSearch.roleMapping.roleMappingsTitle": "ユーザーとロール", - "xpack.enterpriseSearch.roleMapping.roleModalText": "ロールマッピングを取り消すと、現在ログインしているユーザーへのアクセスが取り消される可能性があります。続行する前に、別のロールマッピングを使用して、現在のログインユーザーに適切なアクセスレベルが割り当てられていることを確認し、想定外の動作を回避してください。SAMLで制御されているロールでは、このアクションが直ちに有効にならない場合があります。アクティブなSAMLセッションのユーザーは期限切れになるまでアクセスを保持します。", - "xpack.enterpriseSearch.roleMapping.rolesDisabledDescription": "現在、このデプロイで設定されたすべてのユーザーは{productName}へのフルアクセスが割り当てられています。アクセスを制限し、アクセス権を管理するには、エンタープライズサーチでロールに基づくアクセスを有効にする必要があります。", - "xpack.enterpriseSearch.roleMapping.rolesDisabledNote": "注記:ロールに基づくアクセスを有効にすると、App SearchとWorkplace Searchの両方のアクセスが制限されます。有効にした後は、両方の製品のアクセス管理を確認します(該当する場合)。", - "xpack.enterpriseSearch.roleMapping.rolesDisabledTitle": "ロールに基づくアクセスが無効です", - "xpack.enterpriseSearch.roleMapping.saveRoleMappingButtonLabel": "ロールマッピングの保存", - "xpack.enterpriseSearch.roleMapping.smtpCalloutLabel": "エンタープライズ サーチでは、パーソナライズされた招待が自動的に送信されます", - "xpack.enterpriseSearch.roleMapping.smtpLinkLabel": "SMTP構成が提供されます", - "xpack.enterpriseSearch.roleMapping.updateRoleMappingButtonLabel": "ロールマッピングを更新", - "xpack.enterpriseSearch.roleMapping.updateUserDescription": "粒度の高いアクセス権とアクセス許可を管理", - "xpack.enterpriseSearch.roleMapping.updateUserLabel": "ユーザーを更新", - "xpack.enterpriseSearch.roleMapping.userAddedLabel": "ユーザーが追加されました", - "xpack.enterpriseSearch.roleMapping.userModalText": "ユーザーを取り消すと、ユーザーの属性がネイティブおよびSAMLで統制された認証のロールマッピングに対応していないかぎり、経験へのアクセスが直ちに取り消されます。この場合、必要に応じて、関連付けられたロールマッピングを確認、調整してください。", - "xpack.enterpriseSearch.roleMapping.userModalTitle": "{username}の削除", - "xpack.enterpriseSearch.roleMapping.usernameLabel": "ユーザー名", - "xpack.enterpriseSearch.roleMapping.usernameNoUsersText": "追加できる既存のユーザーはありません。", - "xpack.enterpriseSearch.roleMapping.usersHeadingDescription": "ユーザー管理は、個別または特殊なアクセス権ニーズのために粒度の高いアクセスを提供します。一部のユーザーはこのリストから除外される場合があります。これらにはSAMLなどのフェデレーテッドソースのユーザーが含まれます。これはロールマッピングと、「elastic」や「enterprise_search」ユーザーなどの設定済みのユーザーアカウントで管理されます。", - "xpack.enterpriseSearch.roleMapping.usersHeadingLabel": "新しいユーザーの追加", - "xpack.enterpriseSearch.roleMapping.usersHeadingTitle": "ユーザー", - "xpack.enterpriseSearch.roleMapping.userUpdatedLabel": "ユーザーが更新されました", "xpack.enterpriseSearch.save": "保存", "xpack.enterpriseSearch.schema.addFieldModal.addFieldButtonLabel": "フィールドの追加", "xpack.enterpriseSearch.schema.addFieldModal.description": "追加すると、フィールドはスキーマから削除されます。", @@ -18446,9 +17447,7 @@ "xpack.enterpriseSearch.searchExperiences.guide.featuresTitle": "機能", "xpack.enterpriseSearch.searchExperiences.guide.githubLink": "GitHubのSearch UI", "xpack.enterpriseSearch.searchExperiences.guide.pageTitle": "Search UIで検索エクスペリエンスを構築", - "xpack.enterpriseSearch.searchExperiences.guide.tutorials.appSearch.description": "App SearchとSearch UIで検索エクスペリエンスを構築できます。", "xpack.enterpriseSearch.searchExperiences.guide.tutorials.elasticsearch.description": "ElasticsearchとElasticsearchで検索エクスペリエンスを構築できます。", - "xpack.enterpriseSearch.searchExperiences.guide.tutorials.workplaceSearch.description": "Workplace SearchとSearch UIで検索エクスペリエンスを構築できます。", "xpack.enterpriseSearch.searchExperiences.guide.tutorialsTitle": "チュートリアルを使用してすばやく開始", "xpack.enterpriseSearch.searchExperiences.navTitle": "検索エクスペリエンス", "xpack.enterpriseSearch.searchExperiences.productDescription": "直感的で、魅力的な検索エクスペリエンスを簡単に構築できます。", @@ -18465,7 +17464,6 @@ "xpack.enterpriseSearch.searchNav.mngt": "スタック管理", "xpack.enterpriseSearch.searchNav.otherTools": "その他のツール", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "検索AI", - "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Elastic Webクローラー", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "クリックすると、コネクター説明ポップオーバーが開きます", "xpack.enterpriseSearch.selectConnector.connectorClientBadgeLabel": "セルフマネージド", "xpack.enterpriseSearch.selectConnector.h4.connectorClientsLabel": "セルフマネージドコネクター", @@ -18495,11 +17493,7 @@ "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionExistsError": "コレクション名はすでに存在します。別の名前を選択してください。", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionNotFoundErrorMessage": "分析コレクションが見つかりません", "xpack.enterpriseSearch.server.routes.addConnector.connectorExistsError": "コネクターまたはインデックスはすでに存在します", - "xpack.enterpriseSearch.server.routes.addCrawler.connectorExistsError": "このインデックスのコネクターはすでに存在します", - "xpack.enterpriseSearch.server.routes.addCrawler.crawlerExistsError": "このインデックスのクローラーはすでに存在します", - "xpack.enterpriseSearch.server.routes.addCrawler.indexExistsError": "このインデックスはすでに存在します", "xpack.enterpriseSearch.server.routes.checkKibanaLogsMessage": "{errorMessage}詳細については、Kibanaサーバーログを確認してください。", - "xpack.enterpriseSearch.server.routes.configData.errorMessage": "エンタープライズ サーチからのデータの取得エラー", "xpack.enterpriseSearch.server.routes.connectors.expensive_query_not_allowed_error": "高コストの検索クエリーは許可されません。\"search.allow_expensive_queries\"はfalseに設定されています。", "xpack.enterpriseSearch.server.routes.connectors.generateConfiguration.indexAlreadyExistsError": "一意のコネクター名が見つかりません", "xpack.enterpriseSearch.server.routes.connectors.resource_not_found_error": "ID \"{connectorId}\"のコネクターが見つかりません。", @@ -18510,7 +17504,6 @@ "xpack.enterpriseSearch.server.routes.createSearchApplication.searchApplciationExistsError": "検索アプリケーション名はすでに取得されています。別の名前を選択してください。", "xpack.enterpriseSearch.server.routes.createSearchApplication.searchApplicationInvalidName": "無効な検索アプリケーション名です。{exceptionReason}", "xpack.enterpriseSearch.server.routes.errorLogMessage": "{requestUrl}へのリクエストの解決中にエラーが発生しました:{errorMessage}", - "xpack.enterpriseSearch.server.routes.fetchCrawlerMultipleSchedules.documentNotFoundError": "クローラーデータが見つかりませんでした。", "xpack.enterpriseSearch.server.routes.fetchSearchApplicationFieldCapabilities.error": "検索アプリケーションが見つかりませんでした。", "xpack.enterpriseSearch.server.routes.fetchSearchApplicationFieldCapabilities.missingAliasError": "検索アプリケーションのエイリアスが見つかりません。", "xpack.enterpriseSearch.server.routes.indices.existsErrorLogMessage": "{requestUrl}へのリクエストの解決中にエラーが発生しました", @@ -18518,10 +17511,8 @@ "xpack.enterpriseSearch.server.routes.indices.pipelines.indexMissingError": "インデックス{indexName}が存在しません", "xpack.enterpriseSearch.server.routes.indices.pipelines.pipelineMissingError": "パイプライン{pipelineName}が存在しません", "xpack.enterpriseSearch.server.routes.indices.pipelines.pipelineNotFoundError": "パイプライン{pipelineName}が存在しません", - "xpack.enterpriseSearch.server.routes.recreateConnector.connectorExistsError": "このインデックスのコネクターはすでに存在します", "xpack.enterpriseSearch.server.routes.unauthorizedError": "十分な権限がありません。", "xpack.enterpriseSearch.server.routes.uncaughtExceptionError": "検索でエラーが発生しました。", - "xpack.enterpriseSearch.server.routes.updateHtmlExtraction.noCrawlerFound": "このインデックスのクローラーが見つかりませんでした", "xpack.enterpriseSearch.server.utils.invalidEnumValue": "フィールド{fieldName}の値{value}が正しくありません", "xpack.enterpriseSearch.setupGuide.cloud.step1.instruction1": "Elastic Cloud コンソールにアクセスして、{editDeploymentLink}。", "xpack.enterpriseSearch.setupGuide.cloud.step1.instruction1LinkText": "デプロイの編集", @@ -18598,759 +17589,7 @@ "xpack.enterpriseSearch.whatsNextBox.searchPlaygroundButtonLabel": "Search Playground", "xpack.enterpriseSearch.whatsNextBox.whatsNextPanelDescription": "データを手動で同期したり、繰り返し同期をスケジュールしたり、ドキュメントを表示したりできます。", "xpack.enterpriseSearch.whatsNextBox.whatsNextPanelLabel": "次のステップ", - "xpack.enterpriseSearch.workplaceSearch.accountNav.account.link": "マイアカウント", - "xpack.enterpriseSearch.workplaceSearch.accountNav.logout.link": "ログアウト", - "xpack.enterpriseSearch.workplaceSearch.accountNav.orgDashboard.link": "組織ダッシュボードに移動", - "xpack.enterpriseSearch.workplaceSearch.accountNav.search.link": "検索", - "xpack.enterpriseSearch.workplaceSearch.accountNav.settings.link": "アカウント設定", - "xpack.enterpriseSearch.workplaceSearch.accountNav.sources.link": "コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.accountSettings.description": "アクセス、パスワード、その他のアカウント設定を管理します。", - "xpack.enterpriseSearch.workplaceSearch.accountSettings.title": "アカウント設定", - "xpack.enterpriseSearch.workplaceSearch.activityFeedEmptyDefault.title": "組織には最近のアクティビティがありません", - "xpack.enterpriseSearch.workplaceSearch.activityFeedNamedDefault.title": "{name}には最近のアクティビティがありません", - "xpack.enterpriseSearch.workplaceSearch.add.label": "追加", - "xpack.enterpriseSearch.workplaceSearch.addField.label": "フィールドの追加", - "xpack.enterpriseSearch.workplaceSearch.addSource.addSourceHeader.externalConnectorLabel": "外部でデプロイされたコネクターパッケージ", - "xpack.enterpriseSearch.workplaceSearch.and": "AND", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteLabel": "このAPIキーを削除しますか?この操作は元に戻すことができません。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteTitle": "APIキーの削除", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copied.tooltip": "コピー完了", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiEndpoint.buttonLabel": "API エンドポイントをクリップボードにコピーします。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiKey.buttonLabel": "APIキーをクリップボードにコピーします。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.createdMessage": "APIキー''{name}''が作成されました", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.createKey.buttonLabel": "キーを作成", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.deleteApiKey.buttonDescription": "APIキーの削除", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.deletedMessage": "APIキー''{name}''が削除されました", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyBody": "アプリケーションがElastic Workplace Searchにアクセスすることを許可します。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyButtonLabel": "APIキーの詳細", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyTitle": "最初のAPIキーを作成", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.endpointTitle": "エンドポイント", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.flyoutTitle": "新規キーを作成", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.formHelpText": "キーの名前が作成されます:{name}", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.formLabel": "キー名", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.hideApiKeyLabel": "API キーを非表示", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.keyTitle": "キー", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.namePlaceholder": "例:my-api-key", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.nameTitle": "名前", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.showApiKeyLabel": "API キーを表示", - "xpack.enterpriseSearch.workplaceSearch.baseUri.label": "ベースURL", - "xpack.enterpriseSearch.workplaceSearch.baseUrl.label": "ベースURL", - "xpack.enterpriseSearch.workplaceSearch.betweenLabel": "の間", - "xpack.enterpriseSearch.workplaceSearch.blockLabel": "ブロック", - "xpack.enterpriseSearch.workplaceSearch.clientId.label": "クライアントID", - "xpack.enterpriseSearch.workplaceSearch.clientSecret.label": "クライアントシークレット", - "xpack.enterpriseSearch.workplaceSearch.comfirmModal.title": "確認してください", - "xpack.enterpriseSearch.workplaceSearch.confidential.label": "機密", - "xpack.enterpriseSearch.workplaceSearch.confidential.text": "ネイティブモバイルアプリや単一ページのアプリケーションなど、クライアントシークレットを機密にできない環境では選択解除します。", - "xpack.enterpriseSearch.workplaceSearch.configure.button": "構成", - "xpack.enterpriseSearch.workplaceSearch.confirmChanges.text": "変更の確認", - "xpack.enterpriseSearch.workplaceSearch.connectors.header.description": "構成可能なコネクターすべて。", - "xpack.enterpriseSearch.workplaceSearch.connectors.header.title": "コンテンツソースコネクター", - "xpack.enterpriseSearch.workplaceSearch.consumerKey.label": "コンシューマキー", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.description": "構成を準備するには、コネクターパッケージをすばやくデプロイするために必要なすべての前提条件を{deploymentGuideLink}で確認してください。次のステップでコネクターのURLとAPIキーを設定し、エンタープライズ サーチで構成を確定します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.heading": "{name}は完全にカスタマイズ可能であり、任意のインフラストラクチャーで自己管理されます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.linkLabel": "ドキュメンテーション", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.description": "ユースケースに合わせてコネクターパッケージを構築、修正、デプロイします。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.learnMoreButtonLabel": "詳細", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.registerButtonLabel": "デプロイの登録", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.reviewButtonLabel": "コネクターパッケージの確認", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.title": "探している項目が見つからない場合", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.configCompleted.feedbackCallOutText": "{name}コネクターパッケージのデプロイに関するフィードバックをお寄せください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.apiKeyLabel": "コネクターAPIキー", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.error": "有効なURLを入力してください", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.helpText": "URLの先頭はhttps://でなければなりません", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.insecureTitle": "接続が安全ではありません", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.insecureWarning": "コネクターはHTTP接続を使用しています。この接続はプライベートではありません。このコネクターによって同期される情報は他者から見られる場合があります。情報を保護するには、HTTPSで接続してください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.registerButtonLabel": "デプロイの登録", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.stepTitle": "適切な構成情報を入力する", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.urlLabel": "コネクターURL", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.emptyBody": "管理者がこの組織にソースを追加するときに、検索のソースを使用できます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.emptyTitle": "使用可能なソースがありません", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.newSourceDescription": "ソースを構成して接続するときには、コンテンツプラットフォームから同期された検索可能なコンテンツのある異なるエンティティを作成しています。使用可能ないずれかのソースコネクターを使用して、またはカスタムAPIソースを経由してソースを追加すると、柔軟性を高めることができます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.noSourcesTitle": "最初のコンテンツソースを構成して接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourceDescription": "組織コンテンツソースは組織全体で使用可能にするか、特定のユーザーグループに割り当てることができます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourcesTitle": "組織コンテンツソースを追加", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.placeholder": "ソースのフィルタリング...", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.privateSourceDescription": "新しいソースを接続して、コンテンツとドキュメントを検索エクスペリエンスに追加します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.privateSourcesTitle": "新しいコンテンツソースを追加", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.body": "使用可能なソースを構成するか、独自のソースを構築", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.connectButtonLabel": "接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.customSource.button": "カスタムAPIソース", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.emptyState": "クエリーと一致する使用可能なソースがありません。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.title": "構成で使用可能", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.toolTipContent": "{name}は非公開ソースとして構成でき、プラチナサブスクリプションで使用できます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.back.button": "戻る", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.alt.text": "接続の例", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.configure.button": "デプロイの登録", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.heading": "手順1", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.repositoryLinkLabel": "リポジトリ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.text": "コネクターパッケージ{repositoryLink}には、コネクターフレームワークを理解し、コーディング環境を設定するために必要なすべての内容が揃っています。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.title": "コードの作成または修正", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.documentationLinkLabel": "ドキュメンテーション", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.heading": "手順2", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.text": "コネクターパッケージは、デプロイするインフラストラクチャーで自己管理されます。デプロイを開始するための前提条件については、{documentationLink}を参照してください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.title": "カスタムコネクターパッケージのデプロイ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step3.text": "コネクターパッケージを作成した後は、ここに戻り、コネクターパッケージデプロイの登録、構成の確定、コンテンツソースへの接続を行います。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.steps.title": "カスタムコネクターパッケージを作成、デプロイして、カスタムコンテンツソースからデータを追加したり、ファーストパーティコンテンツソースの動作を修正したりします。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.configureNew.button": "新しいコンテンツソースを構成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.connect.button": "{name}を接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.heading": "{name}が構成されました", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.orgCanConnect.message": "{name}をWorkplace Searchに接続できます", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.personalConnectLink.message": "ユーザーは個人ダッシュボードから{name}アカウントをリンクできます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.button": "非公開コンテンツソースの詳細。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.link": "非公開ソース接続を有効にする", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.message": "必ずセキュリティ設定で{securityLink}してください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.button": "カスタムAPIソースの作成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.createNamedSourceButtonLabel": "{name}の構成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.description": "構成を準備するには、コネクターパッケージをすばやくデプロイするために必要なすべての前提条件を{deploymentGuideLink}で確認してください。{name}コンテンツソースのわかりやすい名前でエンタープライズ サーチの構成を確定し、次のステップで提供されるソースIDでコネクター構成ファイルを更新します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.discussLinkLabel": "ご質問は、こちらのディスカッションをご利用ください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.githubRepoLinkLabel": "ここでコネクターをカスタマイズします。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.heading": "{name}コネクターは完全にカスタマイズ可能であり、任意のインフラストラクチャーで自己管理されます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.linkLabel": "ドキュメンテーション", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.description": "カスタムAPIソースの詳細については、{link}。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.text": "ドキュメントを読む", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.title": "{name}を追加する方法", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.applicationPortal.button": "{name}アプリケーションポータル", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.discuss.buttonLabel": "ご質問は、こちらのディスカッションをご利用ください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.description": "構成可能性と制御を強化するために、カスタムコネクターを設定します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.title": "カスタムコネクター", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.betaLabel": "テクニカルプレビュー", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.description": "高度なユースケースでは、自己管理されたインフラストラクチャーでこのコネクターパッケージをデプロイします。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.recommendedLabel": "推奨", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.reviewButtonLabel": "コネクターパッケージの確認", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.title": "コネクターパッケージ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.button": "接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.description": "このコネクターを使用すると、追加のインフラストラクチャーをデプロイせずにすばやく開始できます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.title": "コネクター", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.alt.text": "接続の例", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button": "{name}の構成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.badge": "ワンタイムアクション", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.heading": "手順1", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.text": "自分またはチームが接続してコンテンツを同期するために使用する安全なOAuthアプリケーションをコンテンツソース経由で設定します。この手順を実行する必要があるのは、コンテンツソースごとに1回だけです。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.title": "OAuthアプリケーション{badge}の構成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.heading": "手順2", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.text": "新しいOAuthアプリケーションを使用して、コンテンツソースの任意の数のインスタンスをWorkplace Searchに接続します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.title": "コンテンツソースの接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.text": "クイック設定を実行すると、すべてのドキュメントが検索可能になります。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.title": "{name}を追加する方法", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.button": "接続の完了", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.label": "同期するGitHub組織を選択", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.accountOnlyTooltip": "非公開コンテンツソース。各ユーザーは独自の個人ダッシュボードからコンテンツソースを追加する必要があります。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.body": "構成が完了し、接続できます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.connectAnotherButton": "別の接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.connectButton": "接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.emptyState": "クエリーと一致する構成されたソースはありません。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.title": "構成されたコンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.unConnectedTooltip": "接続されたソースはありません", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSourcesList.betaBadge": "ベータ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.button": "{name}を接続", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.docPermissionsUnavailable.message": "ドキュメントレベルのアクセス権はこのソースでは使用できません。{link}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.needsPermissions.text": "ドキュメントレベルのアクセス権情報は同期されます。ドキュメントを検索で使用する前には、初期設定の後に、追加の構成が必要です。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.notSynced.text": "接続しているサービスユーザーがアクセス可能なすべてのドキュメントは同期され、組織のユーザーまたはグループのユーザーが使用できるようになります。ドキュメントは直ちに検索で使用できます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.notSynced.title": "ドキュメントレベルのアクセス権は同期されません", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.permissions.label": "ドキュメントレベルのアクセス権同期を有効にする", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.permissions.title": "ドキュメントレベルのアクセス権", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.whichOption.link": "選択すべきオプション", - "xpack.enterpriseSearch.workplaceSearch.contentSource.formSourceAddedSuccessMessage": "{name}が接続されました", - "xpack.enterpriseSearch.workplaceSearch.contentSource.includedFeaturesTitle": "含まれる機能", - "xpack.enterpriseSearch.workplaceSearch.contentSource.reAuthenticate.body": "{name}資格情報は有効ではありません。元の資格情報で再認証して、コンテンツ同期を再開してください。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.reAuthenticate.button": "{name}の再認証", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.baseUrlLabel": "ベースURL", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.button": "構成を保存", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.clientIDLabel": "クライアントID", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.clientSecretLabel": "クライアントシークレット", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.oauthStep1": "組織の{sourceName}アカウントでOAuthアプリを作成する", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.oauthStep2": "適切な構成情報を入力する", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.body1": "エンドポイントは要求を承認できます。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.configureNewSourceButtonLabel": "新しいコンテンツソースを構成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.heading": "{name}が作成されました", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.addField.button": "フィールドの追加", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.description": "一部のドキュメントにインデックスを作成すると、スキーマが作成されます。あらかじめスキーマフィールドを作成するには、以下をクリックします。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.title": "コンテンツソースにはスキーマがありません", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.dataType": "データ型", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.fieldName": "フィールド名", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.heading": "スキーマ変更エラー", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.message": "このスキーマのエラーは見つかりませんでした。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.fieldAdded.message": "新しいフィールドが追加されました。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.noResults.message": "\"{filterValue}\"の結果が見つかりません。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.placeholder": "スキーマフィールドのフィルター...", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.description": "新しいフィールドを追加するか、既存のフィールドの型を変更します", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.title": "ソーススキーマの管理", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.newFieldExists.message": "新しいフィールドがすでに存在します:{fieldName}。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.save.button": "スキーマの保存", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updated.message": "スキーマが更新されました。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.text": "ドキュメントレベルのアクセス権は、定義されたルールに基づいて、ユーザーコンテンツアクセスを管理します。個人またはグループの特定のドキュメントへのアクセスを許可または拒否します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.title": "プラチナライセンスで提供されているドキュメントレベルのアクセス権", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.description": "接続しているサービスユーザーがアクセス可能なすべてのドキュメントは同期され、組織のユーザーまたはグループのユーザーが使用できるようになります。ドキュメントは直ちに検索で使用できます", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.title": "グローバルアクセス権限", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.description": "返された結果は自分に固有で関連性があります。このソースを接続しても、個人データは他の検索ユーザーに公開されません。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.title": "常に非公開", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.description": "メッセージデータと他の情報は、Workplace Searchエクスペリエンスからリアルタイムで検索可能です。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.title": "常に最新", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.description": "次の項目は検索可能です。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.title": "検索可能なコンテンツ", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncedItems.title": "同期された項目", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.text": "このソースは、(初回の同期の後){duration}ごとに{name}から新しいコンテンツを取得します。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.time": "2時間", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.title": "2時間ごとに同期", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.color.label": "色", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.createdBy.label": "作成者", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.description": "カスタムAPIソース検索結果の内容と表示をカスタマイズします。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.title": "表示設定", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.body": "表示設定を構成するには、一部のコンテンツを表示する必要があります。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.title": "まだコンテンツがありません", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.emptyFields.description": "フィールドを追加し、表示する順序に並べ替えます。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.description": "一致するドキュメントは単一の太いカードとして表示されます。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.title": "強調された結果", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.go.button": "Go", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.lastUpdated.heading": "最終更新", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.mediaType.label": "メディアタイプ", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.optionalArea.text": "この領域は任意です", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.preview.title": "プレビュー", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.reset.button": "リセット", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.resultDetail.label": "結果詳細", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.label": "検索結果", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.title": "検索結果設定", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResultsRow.helpText": "この領域は任意です", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.description": "ある程度一致するドキュメントはセットとして表示されます。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.title": "標準結果", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.subtitle.label": "サブタイトル", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.success.message": "表示設定は正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.heading": "タイトル", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.label": "タイトル", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.type.label": "型", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.unsaved.message": "表示設定は保存されていません。終了してよろしいですか?", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.updatedBy.label": "更新者", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.url.label": "URL", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.visibleFields.title": "表示フィールド", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.estimateSummaryLabel": "完了に{estimateDisplay}かかると推定されています。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.frequencyItemLabel": "次の間隔で{label}を実行", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.lastRunLabel": "前回の実行", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.lastRunSummary": "この同期の{lastRunStrong}は{lastRunTime}でした。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartLabel": "次回の実行", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartSummary": "{nextStartStrong}は{nextStartTime}に開始します。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementContentExtractionLabel": "ファイルから全文を同期", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementGlobalConfigLabel": "サムネイルを同期 - グローバル構成レベルでは無効", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementSynchronizeLabel": "このソースを同期", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementThumbnailsLabel": "サムネイルを同期", - "xpack.enterpriseSearch.workplaceSearch.copied.tooltip": "コピー完了", - "xpack.enterpriseSearch.workplaceSearch.copy.tooltip": "クリップボードにコピー", - "xpack.enterpriseSearch.workplaceSearch.copyText": "コピー", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.copied.tooltip": "コピー完了", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.copy.tooltip": "クリップボードにコピー", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.show.tooltip": "{credential}を表示します。", - "xpack.enterpriseSearch.workplaceSearch.credentials.description": "クライアントで次の資格情報を使用して、認証サーバーからアクセストークンを要求します。", - "xpack.enterpriseSearch.workplaceSearch.credentials.title": "資格情報", - "xpack.enterpriseSearch.workplaceSearch.cta": "探索", - "xpack.enterpriseSearch.workplaceSearch.customize.header.description": "一般組織設定をパーソナライズします。", - "xpack.enterpriseSearch.workplaceSearch.customize.header.title": "Workplace Searchのカスタマイズ", - "xpack.enterpriseSearch.workplaceSearch.customize.name.button": "組織名の保存", - "xpack.enterpriseSearch.workplaceSearch.customize.name.label": "組織名", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.documentationLinkLabel": "{name}コネクタードキュメンテーション", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.genericDocumentationHelpText": "任意のセルフマネージドインフラストラクチャーで独自のコネクターを構築してデプロイする方法については、{documentationLink}を確認してください。", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.genericDocumentationLabel": "カスタムAPIソースドキュメント", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.preconfiguredDocumentationHelpText": "{documentationLink}を確認し、任意のセルフマネージドインフラストラクチャーで自己管理するコネクターパッケージをデプロイしてください。", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.preconfiguredRepositoryInstructions": "{githubRepositoryLink}を複製してコネクターを設定", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.repositoryLinkLabel": "{name}コネクターリポジトリ", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.sourceIdentifierHelpText": "デプロイされたコネクターの構成ファイルで次のソースIDと{apiKeyLink}を指定し、ドキュメントを同期します。", - "xpack.enterpriseSearch.workplaceSearch.deployment.title": "デプロイ", - "xpack.enterpriseSearch.workplaceSearch.description.label": "説明", - "xpack.enterpriseSearch.workplaceSearch.documentsHeader": "ドキュメント", - "xpack.enterpriseSearch.workplaceSearch.editField.label": "フィールドの編集", "xpack.enterpriseSearch.workplaceSearch.explorePlatinumFeatures.link": "プラチナ機能の詳細", - "xpack.enterpriseSearch.workplaceSearch.externalConnectorApiKey.label": "コネクターAPIキー", - "xpack.enterpriseSearch.workplaceSearch.externalConnectorUrl.label": "コネクターURL", - "xpack.enterpriseSearch.workplaceSearch.field.label": "フィールド", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.contact": "お問い合わせ", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.description": "フィードバックを送信することにより、お客様は当社の{termsOfService}を読んで同意したこと、およびお客様が上記で提供した詳細情報を使用して当社の関連製品およびサービスについてElasticが{contact}する可能性があることを承諾します。詳細については、{privacyStatementLink}をご覧ください。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.Label": "その他のご意見・ご感想をお聞かせください。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.optionalLabel": "オプション", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readDataPrivacyStatementLink": "Elasticプライバシーステートメント", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readTermsOfService": "利用規約", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.action.Label": "検索分析を追加", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureDescription": "Behavioral Analyticsを使うと、ユーザーの検索やクリックの行動を簡単に分析できることをご存知ですか。Web サイトやアプリケーションをインストルメントし、関連するユーザーのアクションを追跡します。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureName": "Behavioral Analyticsを使用", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureButtonLabel": "コネクターを使用", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureDescription": "Elasticコネクターを使って強力なコンテンツ抽出ができることをご存知ですか。強力で適応性の高い抽出機能を使用して、ファイルからコンテンツを抽出します。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureName": "Elasticコネクターを使用", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureButtonLabel": "コネクターを使用", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureDescription": "Elasticコネクターが利用可能になったことをご存知ですか。データソースのコンテンツを、検索に最適化されたインデックスと同期させることができます。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureName": "Elasticコネクターを使用", - "xpack.enterpriseSearch.workplaceSearch.gateForm.description": "スタンドアロンのWorkplace Search製品は引き続きメンテナンスモードでご利用いただけますが、新しい検索エクスペリエンスにはお勧めできません。その代わりに、当社チームが積極的に開発、改良しているElasticsearchネイティブツールのセットをWorkplace Searchのユースケースに使用することをお勧めします。これらのツールは、Elasticsearchのインデックスを直接扱う柔軟性と複合性を提供します。この再注力の背景については、こちらの{blogUrl}をご覧ください。お客様のユースケースに最適なツールを選択するために、当社はこの推奨ウィザードを作成しました。必要な機能をお知らせいただければ、最適なソリューションをご案内いたします。この時点でまだスタンドアロンのWorkplace Search製品をご利用になりたい場合は、フォームを送信した後でご利用いただけます。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.docLevelPermissions.featureDescription": "Elasticsearchインデックス内のドキュメントへのアクセスを、ユーザーやグループの権限に応じて制限できることをご存知ですか。Elasticのドキュメントレベルのセキュリティが割り当てられたユーザーに対して、許可された検索結果のみを返します。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.docLevelPermissions.featureName": "Elasticコネクターを使用", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.learnMore": "詳細情報", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.subTitle": "あなたの選択に基づくお勧め:", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.title": "Elasticsearchネイティブに相当", - "xpack.enterpriseSearch.workplaceSearch.gateForm.featureOther.Label": "他にどのような機能をお探しですか。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.features.Label": "どのようなWorkplace Search機能をお探しですか。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.features.selectOption": "オプションを選択", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label": "Elasticsearchを改善するためのユーザー調査にご参加ください。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label.No": "いいえ", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label.Yes": "はい", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.optionalLabel": "オプション", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.action.Label": "Search UIで検索エクスペリエンスを構築", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.addOn.learnMoreLabel": "Search UI", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureDescription": "Elasticsearchの能力を最大限に活用した検索機能を備えたアプリケーション!Searchアプリケーション索を構築するか、Search UIを使用して既存のUIに直接統合します。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureName": "検索アプリケーションを作成", - "xpack.enterpriseSearch.workplaceSearch.gateForm.submit": "送信", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.description": "検索結果に対するユーザーのインタラクションを記録してレビュー", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.inputDisplay": "分析", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.title": "分析", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.description": "同期されたソースファイルのコンテンツを抽出して検索可能にする", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.inputDisplay": "コンテンツ抽出", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.title": "コンテンツ抽出", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.description": "同期されたソースファイルのコンテンツを抽出して検索可能にする", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.inputDisplay": "コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.title": "コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.description": "特定のドキュメントへのアクセスを制御", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.inputDisplay": "ドキュメントレベルのアクセス権", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.title": "ドキュメントレベルのアクセス権", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.description": "この一覧に記載されていない機能", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.inputDisplay": "Other", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.title": "Other", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.description": "検索を活用したアプリケーションを簡単に構築", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.inputDisplay": "すぐに使える検索エクスペリエンス", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.title": "すぐに使える検索エクスペリエンス", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.description": "似たような意味を持つ異なる単語や語句を関連付ける", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.inputDisplay": "同義語", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.title": "同義語", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.action.Label": "類義語で検索", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.featureDescription": "類義語で検索することで、検索エクスペリエンスを向上させることができることをご存知ですか。Synonyms APIを使用すると、類義語セットを簡単に作成および管理できます。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.featureName": "Synonyms APIで検索", - "xpack.enterpriseSearch.workplaceSearch.gateForm.title": "はじめに...", - "xpack.enterpriseSearch.workplaceSearch.gateForm.viewBlog": "ブログ", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.heading": "グループを追加", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.label": "グループ名", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.submit.action": "グループを追加", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroupForm.action": "グループを作成", - "xpack.enterpriseSearch.workplaceSearch.groups.clearFilters.action": "フィルターを消去", - "xpack.enterpriseSearch.workplaceSearch.groups.contentSourceCountHeading": "{numSources}件の組織コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.groups.description": "組織コンテンツソースとユーザーをグループに割り当て、さまざまな内部チーム向けに関連する検索エクスペリエンスを作成します。", - "xpack.enterpriseSearch.workplaceSearch.groups.filterGroups.placeholder": "名前でグループをフィルター...", - "xpack.enterpriseSearch.workplaceSearch.groups.filterSources.buttonText": "ソース", - "xpack.enterpriseSearch.workplaceSearch.groups.groupDeleted": "グループ「{groupName}」が正常に削除されました。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "{label}を管理", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "まだ組織コンテンツソースが追加されていない可能性があります。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.title": "おっと!", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.deselectButton.text": "すべて選択解除", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.selectButton.text": "すべて選択", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton": "組織ソースを追加", - "xpack.enterpriseSearch.workplaceSearch.groups.groupNotFound": "ID「{groupId}」のグループが見つかりません。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupPrioritizationUpdated": "組織ソース優先度が正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupRenamed": "このグループ名が正常に「{groupName}」に変更されました。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "組織コンテンツソースが正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupPagination.label": "グループ", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "グループ", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "前回更新日時{updatedAt}。", - "xpack.enterpriseSearch.workplaceSearch.groups.heading": "グループを管理", - "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "ユーザーを招待", - "xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action": "グループを管理", - "xpack.enterpriseSearch.workplaceSearch.groups.newGroupSavedSuccess": "{groupName}が正常に作成されました", - "xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage": "組織コンテンツソースがありません", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveButtonText": "{name}を削除", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "グループはWorkplace Searchから削除されます。{name}を削除してよろしいですか?", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "確認", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "コンテンツソースはこのグループと共有されていません。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription": "「{name}」グループのすべてのユーザーによって検索可能です。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "グループコンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "このグループに割り当てられたユーザーは、上記で定義されたソースのデータとコンテンツへのアクセスを取得します。ユーザーおよびロール領域ではこのグループのユーザー割り当てを管理できます。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.manageSourcesButtonText": "組織コンテンツソースの確認", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.manageUsersButtonText": "ユーザーとロールの管理", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.nameSectionDescription": "このグループの名前をカスタマイズします。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.nameSectionTitle": "グループ名", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeButtonText": "グループを削除", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeSectionDescription": "この操作は元に戻すことができません。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeSectionTitle": "このグループを削除", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.saveNameButtonText": "名前を保存", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.usersSectionTitle": "グループユーザー", - "xpack.enterpriseSearch.workplaceSearch.groups.searchResults.notFoound": "結果が見つかりませんでした。", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerDescription": "グループコンテンツソース全体で相対ドキュメント重要度を調整します。", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerTitle": "組織コンテンツソースの優先度", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.priorityTableHeader": "関連性優先度", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.sourceTableHeader": "送信元", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateBody": "2つ以上のソースを{groupName}と共有し、ソース優先度をカスタマイズします。", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateButtonText": "組織コンテンツソースの追加", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateTitle": "ソースはこのグループと共有されていません", - "xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalLabel": "組織コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalTitle": "{groupName}と共有するコンテンツソースを選択", - "xpack.enterpriseSearch.workplaceSearch.keepEditing.button": "編集を続行", - "xpack.enterpriseSearch.workplaceSearch.label.label": "ラベル", - "xpack.enterpriseSearch.workplaceSearch.name.label": "名前", - "xpack.enterpriseSearch.workplaceSearch.nav.addSource": "ソースの追加", - "xpack.enterpriseSearch.workplaceSearch.nav.apiKeys": "APIキー", - "xpack.enterpriseSearch.workplaceSearch.nav.content": "コンテンツ", - "xpack.enterpriseSearch.workplaceSearch.nav.displaySettings": "表示設定", - "xpack.enterpriseSearch.workplaceSearch.nav.groups": "グループ", - "xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview": "概要", - "xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization": "ソースの優先度", - "xpack.enterpriseSearch.workplaceSearch.nav.overview": "概要", - "xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard": "個人のダッシュボードを表示", - "xpack.enterpriseSearch.workplaceSearch.nav.roleMappings": "ユーザーとロール", - "xpack.enterpriseSearch.workplaceSearch.nav.schema": "スキーマ", - "xpack.enterpriseSearch.workplaceSearch.nav.searchApplication": "検索アプリケーションに移動", - "xpack.enterpriseSearch.workplaceSearch.nav.security": "セキュリティ", - "xpack.enterpriseSearch.workplaceSearch.nav.settings": "設定", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsCustomize": "カスタマイズ", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsOauth": "OAuthアプリケーション", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsSourcePrioritization": "コンテンツソースコネクター", - "xpack.enterpriseSearch.workplaceSearch.nav.sources": "ソース", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronization": "同期", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationAssetsAndObjects": "アセットとオブジェクト", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationFrequency": "頻度", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "Workplace Search検索APIを安全に使用するために、OAuthアプリケーションを構成します。プラチナライセンスにアップグレードして、検索APIを有効にし、OAuthアプリケーションを作成します。", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "カスタム検索アプリケーションのOAuthを構成", - "xpack.enterpriseSearch.workplaceSearch.oauth.description": "組織のOAuthクライアントを作成します。", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationDescription": "{strongClientName}によるアカウントの使用を許可しますか?", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationTitle": "許可が必要です", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizeButtonLabel": "許可", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.denyButtonLabel": "拒否", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.httpRedirectWarningMessage": "このアプリケーションは保護されていないリダイレクトURI(http)を使用しています", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.scopesLeadInMessage": "このアプリケーションでできること", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.searchScopeDescription": "データの検索", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.unknownScopeDescription": "データの{unknownAction}", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.writeScopeDescription": "データの変更", - "xpack.enterpriseSearch.workplaceSearch.oauthPersisted.description": "組織のOAuthクライアント資格情報にアクセスし、OAuth設定を管理します。", - "xpack.enterpriseSearch.workplaceSearch.ok.button": "OK", - "xpack.enterpriseSearch.workplaceSearch.onLabel": "日付", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "アクティブなユーザー", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "招待", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.organizationalSources": "組織のコンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "プライベートソース", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "使用統計情報", - "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "組織名を指定", - "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "同僚を招待する前に、組織名を指定し、認識しやすくしてください。", - "xpack.enterpriseSearch.workplaceSearch.overviewHeader.description": "組織の統計情報とアクティビティ", - "xpack.enterpriseSearch.workplaceSearch.overviewHeader.title": "組織概要", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.description": "次の手順を完了し、組織を設定してください。", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.title": "Workplace Searchの基本", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.description": "検索を開始するには、組織の組織ソースを追加してください。", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.title": "組織のコンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.description": "検索できるように、同僚をこの組織に招待します。", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteFirstUsers.button": "ユーザーを招待", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteMoreUsers.button": "その他のユーザーを招待", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.title": "ユーザーと招待", - "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "検索できるように、同僚を招待しました。", - "xpack.enterpriseSearch.workplaceSearch.personalDashboard.readOnlyMode.warning": "現在、定期メンテナンス中のため、Workplace Searchは検索でのみ使用できます。詳細については、システム管理者に連絡してください。", - "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "ソースに接続できませんでした。ヘルプについては管理者に問い合わせてください。エラーメッセージ:{error}", - "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "プラチナ機能", - "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "非公開ソースにはプラチナライセンスが必要です。", - "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "非公開ソース", - "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "非公開ソース", - "xpack.enterpriseSearch.workplaceSearch.productCardDescription": "社内のビジネスチーム向けにカスタマイズされたWorkplace Searchは、一般的な生産性向上ツールやサードパーティのソースに即座に接続し、単一の統合プラットフォームを構築できます。", - "xpack.enterpriseSearch.workplaceSearch.productCta": "Workplace Searchを開く", - "xpack.enterpriseSearch.workplaceSearch.productDescription": "仮想ワークプレイスで使用可能な、すべてのドキュメント、ファイル、ソースを検索します。", - "xpack.enterpriseSearch.workplaceSearch.productName": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.publicKey.label": "公開鍵", - "xpack.enterpriseSearch.workplaceSearch.recentActivity.title": "最近のアクティビティ", - "xpack.enterpriseSearch.workplaceSearch.recentActivitySourceLink.linkLabel": "ソースを表示", - "xpack.enterpriseSearch.workplaceSearch.redirectHelp.text": "1行に1つのURIを記述します。", - "xpack.enterpriseSearch.workplaceSearch.redirectInsecureError.text": "保護されていないリダイレクトURI(http)の使用は推奨されません。", - "xpack.enterpriseSearch.workplaceSearch.redirectNativeHelp.text": "ローカル開発URIでは、次の形式を使用します", - "xpack.enterpriseSearch.workplaceSearch.redirectSecureError.text": "重複するリダイレクトURIは使用できません。", - "xpack.enterpriseSearch.workplaceSearch.redirectURIs.label": "リダイレクトURI", - "xpack.enterpriseSearch.workplaceSearch.remove.button": "削除", - "xpack.enterpriseSearch.workplaceSearch.removeField.label": "フィールドの削除", - "xpack.enterpriseSearch.workplaceSearch.reset.button": "リセット", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.adminRoleTypeDescription": "管理者は、コンテンツソース、グループ、ユーザー管理機能など、すべての組織レベルの設定に無制限にアクセスできます。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.allGroupsDescription": "すべてのグループへの割り当てには、後から作成および管理されるすべての現在および将来のグループが含まれます。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.allGroupsLabel": "すべてのグループに割り当て", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.defaultGroupName": "デフォルト", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentInvalidError": "1つ以上の割り当てられたグループが必要です。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentLabel": "グループ割り当て", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsTableHeader": "グループアクセス", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.specificGroupsDescription": "選択したグループのセットに静的に割り当てます。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.specificGroupsLabel": "特定のグループに割り当て", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.userRoleTypeDescription": "ユーザーの機能アクセスは検索インターフェースと個人設定管理に制限されます。", - "xpack.enterpriseSearch.workplaceSearch.roleMappingCreatedMessage": "ロールマッピングが正常に作成されました。", - "xpack.enterpriseSearch.workplaceSearch.roleMappingDeletedMessage": "ロールマッピングが正常に削除されました", - "xpack.enterpriseSearch.workplaceSearch.roleMappingUpdatedMessage": "ロールマッピングが正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.saveChanges.button": "変更を保存", - "xpack.enterpriseSearch.workplaceSearch.saveSettings.button": "設定を保存", - "xpack.enterpriseSearch.workplaceSearch.searchableHeader": "検索可能", - "xpack.enterpriseSearch.workplaceSearch.security.privateSources.description": "非公開ソースは組織のユーザーによって接続され、パーソナライズされた検索エクスペリエンスを作成します。", - "xpack.enterpriseSearch.workplaceSearch.security.privateSourcesToggle.description": "組織の非公開ソースを有効にする", - "xpack.enterpriseSearch.workplaceSearch.security.privateSourcesUpdateConfirmation.text": "非公開ソースに対する更新は、直ちに有効になります。", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.description": "構成されると、リモート非公開ソースは{enabledStrong}。ユーザーは直ちに個人ダッシュボードからソースを接続できます。", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.enabledStrong": "デフォルトで有効です", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.title": "リモート非公開ソースはまだ構成されていません", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesTable.description": "リモートソースでは同期、保存されるディスクのデータが限られているため、ストレージリソースへの影響が少なくなります。", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesToggle.text": "リモート非公開ソースを有効にする", - "xpack.enterpriseSearch.workplaceSearch.security.sourceRestrictionsSuccess.message": "ソース制限が正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.description": "構成されると、標準非公開ソースは{notEnabledStrong}。ユーザーが個人ダッシュボードからソースを接続する前に、標準非公開ソースを有効にする必要があります。", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.notEnabledStrong": "デフォルトでは有効ではありません", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.title": "標準非公開ソースはまだ構成されていません", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesTable.description": "標準ソースはディスク上の検索可能なすべてのデータを同期、保存するため、ストレージリソースに直接影響します。", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesToggle.text": "標準非公開ソースを有効にする", - "xpack.enterpriseSearch.workplaceSearch.security.unsavedChanges.message": "非公開ソース設定が保存されました。終了してよろしいですか?", - "xpack.enterpriseSearch.workplaceSearch.settings.brandText": "ブランド", - "xpack.enterpriseSearch.workplaceSearch.settings.configRemoved.message": "{name}の構成が正常に削除されました。", - "xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfig.message": "{name}の構成を削除しますか?", - "xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfigTitle": "構成を削除", - "xpack.enterpriseSearch.workplaceSearch.settings.feedbackCallOutText": "{name}コネクターパッケージのデプロイに関するフィードバックをお寄せください。", - "xpack.enterpriseSearch.workplaceSearch.settings.iconDescription": "小さい画面サイズおよびブラウザーアイコンのブランド要素として使用されます", - "xpack.enterpriseSearch.workplaceSearch.settings.iconHelpText": "最大ファイルサイズは2MB、推奨アスペクト比は1:1です。PNGファイルのみがサポートされています。", - "xpack.enterpriseSearch.workplaceSearch.settings.iconText": "アイコン", - "xpack.enterpriseSearch.workplaceSearch.settings.logoDescription": "構築済みの検索アプリケーションでメインの視覚的なブランディング要素として使用されます", - "xpack.enterpriseSearch.workplaceSearch.settings.logoHelpText": "最大ファイルサイズは2MBです。PNGファイルのみがサポートされています。", - "xpack.enterpriseSearch.workplaceSearch.settings.logoText": "ロゴ", - "xpack.enterpriseSearch.workplaceSearch.settings.oauthAppUpdated.message": "アプリケーションが正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.settings.organizationLabel": "組織別", - "xpack.enterpriseSearch.workplaceSearch.settings.orgUpdated.message": "組織が正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.settings.resetIconDescription": "アイコンをデフォルトのWorkplace Searchブランドにリセットしようとしています。", - "xpack.enterpriseSearch.workplaceSearch.settings.resetImageConfirmationText": "実行しますか?", - "xpack.enterpriseSearch.workplaceSearch.settings.resetImageTitle": "デフォルトブランドにリセット", - "xpack.enterpriseSearch.workplaceSearch.settings.resetLogoDescription": "ロゴをデフォルトのWorkplace Searchブランドにリセットしようとしています。", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.button": "Workplace Searchの基本", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.description": "Google Drive、Salesforceなどのコンテンツプラットフォームを、パーソナライズされた検索エクスペリエンスに統合します。", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.imageAlt": "Workplace Searchの基本というガイドでは、Workplace Searchを起動して実行する方法について説明します。", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.notConfigured": "Workplace SearchはKibanaでは構成されていません。このページの手順に従ってください。", - "xpack.enterpriseSearch.workplaceSearch.source.text": "送信元", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.baseUrlLabel": "ベースURL", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.clientIDLabel": "クライアントID", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.clientSecretLabel": "クライアントシークレット", - "xpack.enterpriseSearch.workplaceSearch.sourceIdentifier.sourceIdentifierFieldLabel": "ソース識別子", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.detailsLabel": "詳細", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.reauthenticateStatusLinkLabel": "再認証", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteLabel": "リモート", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteTooltip": "リモートソースは直接ソースの検索サービスに依存しています。コンテンツはWorkplace Searchでインデックスされません。結果の速度と完全性はサードパーティサービスの正常性とパフォーマンスの機能です。", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.searchableToggleLabel": "ソース検索可能トグル", - "xpack.enterpriseSearch.workplaceSearch.sources.additionalConfig.heading": "追加の構成が必要", - "xpack.enterpriseSearch.workplaceSearch.sources.apiKeyLabel": "API キー", - "xpack.enterpriseSearch.workplaceSearch.sources.apiKeysTitle": "APIキー", - "xpack.enterpriseSearch.workplaceSearch.sources.applicationLinkTitles.github": "GitHub開発者ポータル", - "xpack.enterpriseSearch.workplaceSearch.sources.baseUrlTitles.github": "GitHub Enterprise URL", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedEmptyStateDescription": "適切な時刻にのみ実行するには、ブロックされた時間帯を追加します。", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedEmptyStateTitle": "ブロックされた時間帯はありません", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedWindowsTitle": "ブロックされた時間帯", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.accountManagement": "アカウント管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.atlassian": "Atlassian", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.bugTracking": "バグ追跡", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.chat": "チャット", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.cloud": "クラウド", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.codeRepository": "コードリポジトリ", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.collaboration": "コラボレーション", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.communication": "コミュニケーション", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.crm": "CRM", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.customerRelationshipManagement": "顧客関係管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.customerService": "カスタマーサービス", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.email": "メール", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.fileSharing": "ファイル共有", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.google": "Google", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.gsuite": "GSuite", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.help": "ヘルプ", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.helpdesk": "ヘルプデスク", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.instantMessaging": "インスタントメッセージング", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.intranet": "イントラネット", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.microsoft": "Microsoft", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.office": "Office 365", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.productivity": "生産性", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.projectManagement": "プロジェクト管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.software": "ソフトウェア", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.storage": "ストレージ", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.ticketing": "チケット", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.versionControl": "バージョン管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.wiki": "Wiki", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.workflow": "ワークフロー", - "xpack.enterpriseSearch.workplaceSearch.sources.config.link": "コネクター設定の編集", - "xpack.enterpriseSearch.workplaceSearch.sources.config.title": "コンテンツソース構成", - "xpack.enterpriseSearch.workplaceSearch.sources.configuration.title": "構成", - "xpack.enterpriseSearch.workplaceSearch.sources.contentLoading.text": "コンテンツを読み込んでいます...", - "xpack.enterpriseSearch.workplaceSearch.sources.contentSummary.title": "コンテンツ概要", - "xpack.enterpriseSearch.workplaceSearch.sources.contentSummaryLoading.text": "概要詳細を読み込んでいます...", - "xpack.enterpriseSearch.workplaceSearch.sources.contentType.header": "コンテンツタイプ", - "xpack.enterpriseSearch.workplaceSearch.sources.created.label": "作成済み:", - "xpack.enterpriseSearch.workplaceSearch.sources.customCallout.title": "カスタムソースの基本", - "xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.link": "ドキュメンテーション", - "xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.text": "コンテンツの追加の詳細については、{documentationLink}を参照してください", - "xpack.enterpriseSearch.workplaceSearch.sources.deletionSyncDescription": "コンテンツソースに存在しないドキュメントを削除します", - "xpack.enterpriseSearch.workplaceSearch.sources.deletionSyncLabel": "削除同期", - "xpack.enterpriseSearch.workplaceSearch.sources.docPermissions.description": "ドキュメントレベルのアクセス権は、個別またはグループの属性でアクセスコンテンツを管理します。特定のドキュメントへのアクセスを許可または拒否。", - "xpack.enterpriseSearch.workplaceSearch.sources.documentation": "ドキュメント", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.text": "ドキュメントレベルのアクセス権を使用", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.title": "ドキュメントレベルのアクセス権", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsDisabled.text": "このソースでは無効", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsLink": "ドキュメントレベルのアクセス権構成の詳細", - "xpack.enterpriseSearch.workplaceSearch.sources.downloadDiagnosticButton": "診断バンドルのダウンロード", - "xpack.enterpriseSearch.workplaceSearch.sources.emptyActivity.title": "最近のアクティビティがありません", - "xpack.enterpriseSearch.workplaceSearch.sources.event.header": "イベント", - "xpack.enterpriseSearch.workplaceSearch.sources.externalIdentities.link": "外部ID API", - "xpack.enterpriseSearch.workplaceSearch.sources.externalIdentities.text": "{externalIdentitiesLink}を使用して、ユーザーアクセスマッピングを構成する必要があります。詳細については、ガイドをお読みください。", - "xpack.enterpriseSearch.workplaceSearch.sources.feedbackCallOutText": "コネクターパッケージのデプロイに関するフィードバックがある場合お寄せください。", - "xpack.enterpriseSearch.workplaceSearch.sources.feedbackLinkLabel": "{name}コネクターのデプロイに関するフィードバックをお寄せください。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.additionalConfigurationNeeded": "このソースは追加の構成が必要です。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConfigUpdated": "構成が正常に更新されました。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConnected": "正常に{sourceName}を接続しました。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceNameChanged": "名前が正常に{sourceName}に変更されました。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceRemoved": "{sourceName}が正常に削除されました。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.externalConnectorCreated": "コネクターパッケージデプロイが正常に登録されました。", - "xpack.enterpriseSearch.workplaceSearch.sources.fullSyncDescription": "コンテンツソースからすべてのドキュメントを取得します", - "xpack.enterpriseSearch.workplaceSearch.sources.fullSyncLabel": "完全同期", - "xpack.enterpriseSearch.workplaceSearch.sources.groupAccess.title": "グループアクセス", - "xpack.enterpriseSearch.workplaceSearch.sources.helpText.custom": "カスタムAPIソースを作成するには、人間が読み取れるわかりやすい名前を入力します。この名前はさまざまな検索エクスペリエンスと管理インターフェースでそのまま表示されます。", - "xpack.enterpriseSearch.workplaceSearch.sources.id.label": "ソース識別子", - "xpack.enterpriseSearch.workplaceSearch.sources.incrementalSyncDescription": "前回の同期ジョブ以降に発生したドキュメント/更新を取得します", - "xpack.enterpriseSearch.workplaceSearch.sources.incrementalSyncLabel": "差分同期", - "xpack.enterpriseSearch.workplaceSearch.sources.indexingRulesTable.includeEverythingMessage": "このソースの他のすべての項目を含める", - "xpack.enterpriseSearch.workplaceSearch.sources.items.header": "アイテム", - "xpack.enterpriseSearch.workplaceSearch.sources.learnCustom.features.button": "プラチナ機能の詳細", - "xpack.enterpriseSearch.workplaceSearch.sources.learnMore.link": "詳細", - "xpack.enterpriseSearch.workplaceSearch.sources.learnMore.text": "アクセス権については、{learnMoreLink}", - "xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.description": "詳細については、検索エクスペリエンス管理者に問い合わせてください。", - "xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.title": "非公開ソースは使用できません", - "xpack.enterpriseSearch.workplaceSearch.sources.nextSyncRunningMessage": "現在実行中のジョブが完了したらすぐに実行", - "xpack.enterpriseSearch.workplaceSearch.sources.noContent.title": "コンテンツがありません", - "xpack.enterpriseSearch.workplaceSearch.sources.noContentEmpty.message": "このソースにはコンテンツがありません", - "xpack.enterpriseSearch.workplaceSearch.sources.noContentForValue.message": "''{contentFilterValue}''の結果がありません", - "xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage": "ソースが見つかりません。", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.accounts": "アカウント", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.allFiles": "すべてのファイル(画像、PDF、スプレッドシート、テキストドキュメント、プレゼンテーションを含む)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.allStoredFiles": "保存されたすべてのファイル(画像、動画、PDF、スプレッドシート、テキストドキュメント、プレゼンテーションを含む)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.articles": "記事", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.attachments": "添付ファイル", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.blogPosts": "ブログ記事", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.bugs": "不具合", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.campaigns": "キャンペーン", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.cases": "ケース(フィードとコメントを含む)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.contacts": "連絡先", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.directMessages": "ダイレクトメッセージ", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.emails": "電子メール", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.epics": "エピック", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.files": "ファイル(Markdownのみ)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.folders": "フォルダー", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.gSuiteFiles": "Google G Suiteドキュメント(ドキュメント、スプレッドシート、スライド)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.incidents": "インシデント", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.issues": "問題(コメントを含む)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.items": "アイテム", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.leads": "リード", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.opportunities": "機会", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.pages": "ページ", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.privateMessages": "自分がアクティブな参加者である非公開チャネルメッセージ", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.projects": "プロジェクト", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.publicMessages": "公開チャネルメッセージ", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.pullRequests": "プル要求(コメントを含む)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.repositoryList": "リポジトリリスト", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.sites": "サイト", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.spaces": "スペース", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.stories": "ストーリー", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.tasks": "タスク", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.tickets": "チケット", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.users": "ユーザー", - "xpack.enterpriseSearch.workplaceSearch.sources.org.description": "組織コンテンツソースは組織全体で使用可能にするか、特定のユーザーグループに割り当てることができます。", - "xpack.enterpriseSearch.workplaceSearch.sources.org.empty.description": "コンテンツソースが自分と共有されたら、ここに表示され、検索エクスペリエンスで使用できます。", - "xpack.enterpriseSearch.workplaceSearch.sources.org.empty.title": "コンテンツソースがありません", - "xpack.enterpriseSearch.workplaceSearch.sources.org.link": "組織コンテンツソースを追加", - "xpack.enterpriseSearch.workplaceSearch.sources.org.title": "組織のコンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.sources.permissionsSyncDescription": "前回の同期ジョブ以降のすべてのアクセス権の変更を取得します", - "xpack.enterpriseSearch.workplaceSearch.sources.permissionsSyncLabel": "アクセス権同期", - "xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.description": "接続されたすべての非公開ソースのステータスを確認し、アカウントの非公開ソースを管理します。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.title": "非公開コンテンツソースの管理", - "xpack.enterpriseSearch.workplaceSearch.sources.private.empty.title": "非公開ソースがありません", - "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "非公開コンテンツソースは自分のみが使用できます。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "自分の非公開コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "非公開コンテンツソースを追加", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateOrg.header.description": "{groups, plural, other {グループ}} {groupsSentence}経由で{newline}次のソースにアクセスできます。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateOrg.header.title": "組織コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "グループと共有するすべてのソースのステータスを確認します。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "グループソースの確認", - "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "検索できます", - "xpack.enterpriseSearch.workplaceSearch.sources.remoteSource.label": "リモートソース", - "xpack.enterpriseSearch.workplaceSearch.sources.remove.description": "この操作は元に戻すことができません。", - "xpack.enterpriseSearch.workplaceSearch.sources.remove.title": "このコンテンツソースを削除", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.description": "このコンテンツソースの名前をカスタマイズします。", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "設定", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "コンテンツソース名", - "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "ソースドキュメントはWorkplace Searchから削除されます。{lineBreak}{name}を削除しますか?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAddRuleLabel": "インデックスルールを追加", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAssetsLabel": "アセット", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsDescription": "以下の粒度の高いコントロールを使用して、同期されるドキュメントを柔軟に管理し、検索可能にします。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsLearnMoreLink": "同期オブジェクトタイプの詳細", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsDescription": "インデックスルールを追加して、{contentSourceName}から同期されるデータをカスタマイズします。デフォルトではすべてが含まれます。ドキュメントは、リストの最上位から下に向かって、構成されたインデックスルールのセットに対して検証されます。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsLabel": "オブジェクト", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableExcludeLabel": "除外", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableFileLabel": "ファイルタイプ", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableIncludeLabel": "含める", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableItemLabel": "項目", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePathLabel": "パス", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePolicyLabel": "ポリシー", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableRuleLabel": "ルール", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableValueLabel": "値", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsSyncLearnMoreLink": "インデックスルールのカスタマイズの詳細をご覧ください。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceBlockedTimeWindowsLinkLabel": "ブロックされた時間帯", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.filter.placeholder": "コンテンツのフィルター...", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.search.placeholder": "コンテンツの検索...", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "ソースコンテンツ", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "プラチナライセンスの詳細", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "組織のライセンスレベルが変更されました。データは安全ですが、ドキュメントレベルのアクセス権はサポートされなくなり、このソースの検索は無効になっています。このソースを再有効化するには、プラチナライセンスにアップグレードしてください。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.title": "コンテンツソースが無効です", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceFrequencyDescription": "Workplace searchからこのコンテンツソースへのデータ同期の頻度を管理します。同期の間隔を短くして、データが最新の状態であることを保証します。同期の間隔を長くして、サードパーティのサーバーへの負荷を減らします。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceName.label": "ソース名", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.box": "Box", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluence": "Confluence", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluenceConnectorPackage": "Confluenceコネクターパッケージ", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluenceServer": "Confluence(サーバー)", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.custom": "カスタムAPIソース", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.customConnectorPackage": "カスタムコネクターパッケージ", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.dropbox": "Dropbox", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.github": "GitHub", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.githubEnterprise": "GitHub Enterprise Server", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.gmail": "Gmail", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.googleDrive": "Google Drive", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.jira": "Jira", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.jiraServer": "Jira(サーバー)", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.networkDrive": "ネットワークドライブ", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.oneDrive": "OneDrive", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.outlook": "Outlook", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.salesforce": "Salesforce", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.salesforceSandbox": "Salesforce Sandbox", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.serviceNow": "ServiceNow", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePoint": "SharePoint Online", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePointConnectorPackage": "SharePoint Onlineコネクターパッケージ", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePointServer": "SharePoint Server", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.slack": "Slack", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.teams": "Teams", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zendesk": "Zendesk", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zoom": "ズーム", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceOverviewTitle": "ソース概要", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSettings.pemKeyPrompText": "新しい.pemファイルをアップロードし、秘密鍵をローテーション", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmMessage": "この要求を続行し、他のすべての同期を停止しますか?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmTitle": "新しいコンテンツ同期を開始しますか?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncFrequencyLinkLabel": "同期頻度", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncFrequencyTitle": "同期頻度", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationButtonLabel": "コンテンツの同期", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationDescription": "このコンテンツソースからWorkplace Searchへのデータの同期を有効または無効にします。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationFrequencyTitle": "同期頻度", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationTitle": "同期", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleDescription": "ソースコンテンツは自動的に同期状態が維持されます。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleLabel": "このソースを同期", - "xpack.enterpriseSearch.workplaceSearch.sources.status.header": "ステータス", - "xpack.enterpriseSearch.workplaceSearch.sources.status.heading": "すべて問題なし", - "xpack.enterpriseSearch.workplaceSearch.sources.status.label": "ステータス:", - "xpack.enterpriseSearch.workplaceSearch.sources.status.text": "エンドポイントは要求を承認できます。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsButton": "診断データをダウンロード", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsDescription": "アクティブ同期プロセスのトラブルシューティングで関連する診断データを取得します。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsTitle": "診断の同期", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationCallout": "{syncFrequencyLink}または{blockTimeWindowsLink}を構成します。", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationDisabledDescription": "同期制御を有効にするには、管理者に問い合わせてください。", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationDisabledTitle": "ソース同期が無効です。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncSettingsUpdatedMessage": "ソース同期設定が更新されました。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncUnsavedChangesMessage": "変更は保存されていません。終了してよろしいですか?", - "xpack.enterpriseSearch.workplaceSearch.sources.time.header": "時間", - "xpack.enterpriseSearch.workplaceSearch.sources.totalDocuments.label": "合計ドキュメント数", - "xpack.enterpriseSearch.workplaceSearch.sources.understandButton": "理解します", - "xpack.enterpriseSearch.workplaceSearch.sources.utcLabel": "現在のUTC時刻:{utcTime}", - "xpack.enterpriseSearch.workplaceSearch.sources.utcTitle": "すべての時刻はUTC表記です", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addFirstSources.button": "ソースの追加", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addMoreSources.button": "その他のソースを追加", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description": "{sourcesCount, number}{sourcesCount, plural, other {組織ソース}}を追加しました。検索をご利用ください。", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.docPermissions.description": "ユーザーおよびグループマッピングが構成されるまでは、ドキュメントをWorkplace Searchから検索できません。{documentPermissionsLink}", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading": "{addedSourceName}には追加の構成が必要です", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.success": "{addedSourceName}は正常に接続されました。初期コンテンツ同期がすでに実行中です。ドキュメントレベルのアクセス権情報を同期することを選択したので、{externalIdentitiesLink}を使用してユーザーおよびグループマッピングを指定する必要があります。", - "xpack.enterpriseSearch.workplaceSearch.statusPopoverTooltip": "情報を表示するにはクリックしてください", - "xpack.enterpriseSearch.workplaceSearch.title": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.update.label": "更新", - "xpack.enterpriseSearch.workplaceSearch.url.label": "URL", "xpack.eventLog.savedObjectProviderRegistry.getProvidersClient.noDefaultProvider": "イベントログにはデフォルトプロバイダーが必要です。", "xpack.exploratoryView..synthetics.addDataButtonLabel": "Syntheticsデータの追加", "xpack.exploratoryView.alerts.alertStarted": "タイムスタンプ", @@ -32069,17 +30308,6 @@ "xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName": "割り当てなし", "xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel": "未割り当てプライマリ", "xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel": "未割り当てレプリカ", - "xpack.monitoring.entSearch.overview.appSearchEngines": "App Searchエンジン", - "xpack.monitoring.entSearch.overview.appSearchSummary": "App Searchの概要", - "xpack.monitoring.entSearch.overview.heading": "エンタープライズ サーチの概要", - "xpack.monitoring.entSearch.overview.instances": "インスタンス", - "xpack.monitoring.entSearch.overview.lowLevelSummary": "低レベルリソース使用状況概要", - "xpack.monitoring.entSearch.overview.networkingSummary": "ネットワークトラフィックの概要", - "xpack.monitoring.entSearch.overview.pageTitle": "エンタープライズ サーチの概要", - "xpack.monitoring.entSearch.overview.routeTitle": "エンタープライズ サーチ - 概要", - "xpack.monitoring.entSearch.overview.workplaceSearchOrgSources": "組織コンテンツソース", - "xpack.monitoring.entSearch.overview.workplaceSearchPrivateSources": "非公開コンテンツソース", - "xpack.monitoring.entSearch.overview.workplaceSearchSummary": "Workplace Searchの概要", "xpack.monitoring.errors.connectionErrorMessage": "接続エラー:Elasticsearch 監視クラスターのネットワーク接続を確認し、詳細は Kibana のログをご覧ください。", "xpack.monitoring.errors.insufficientUserErrorMessage": "データの監視に必要なユーザーパーミッションがありません", "xpack.monitoring.errors.invalidAuthErrorMessage": "クラスターの監視に無効な認証です", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 1de6205358242..af0c939a938c3 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -3801,7 +3801,6 @@ "guidedOnboarding.quitGuideModal.quitButtonLabel": "退出指南", "guidedOnboardingPackage.gettingStarted.cards.aiSearch.title": "构建 AI 驱动式搜索体验", "guidedOnboardingPackage.gettingStarted.cards.apmObservability.title": "监测我的应用程序{lineBreak}性能(APM/跟踪)", - "guidedOnboardingPackage.gettingStarted.cards.appSearch.title": "在 Elasticsearch 之上{lineBreak}构建应用程序", "guidedOnboardingPackage.gettingStarted.cards.cloudSecurity.title": "借助云{lineBreak}安全态势管理 (CSPM) 保护我的云资产", "guidedOnboardingPackage.gettingStarted.cards.completeLabel": "指南完成", "guidedOnboardingPackage.gettingStarted.cards.databaseSearch.title": "跨数据库和{lineBreak}业务系统进行搜索", @@ -15703,906 +15702,11 @@ "xpack.enterpriseSearch.apiKeyConfig.newApiKeyCreatedCalloutLabel": "已成功创建新的 API 密钥", "xpack.enterpriseSearch.applications.navTitle": "构建", "xpack.enterpriseSearch.applications.productName": "应用程序", - "xpack.enterpriseSearch.appSearch.actions.restoreDefaultsButonLabel": "还原默认值", - "xpack.enterpriseSearch.appSearch.adminRoleTypeDescription": "管理员可以执行任何操作,但不包括管理帐户设置。", - "xpack.enterpriseSearch.appSearch.allEnginesDescription": "分配给所有引擎包括之后创建和管理的所有当前和未来引擎。", - "xpack.enterpriseSearch.appSearch.allEnginesLabel": "分配给所有引擎", - "xpack.enterpriseSearch.appSearch.analystRoleTypeDescription": "分析人员仅可以查看文档、查询测试器和分析。", - "xpack.enterpriseSearch.appSearch.audit.title": "审计", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.description": "可以将多个域添加到此引擎的网络爬虫。在此添加其他域并从'管理'页面修改入口点和爬网规则。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.openButtonLabel": "添加域", - "xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.title": "添加新域", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationFailureMessage": "因为'索引限制'检查失败,无法验证内容。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationLabel": "内容验证", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.entryPointLabel": "网络爬虫入口点已设置为 {entryPointValue}", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.errorsTitle": "出问题了。请解决这些错误,然后重试。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationDescription": "在解决以上错误之前,网络爬虫将无法索引此域上的任何内容。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationTitle": "忽略验证失败并继续", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsFailureMessage": "无法确定索引限制,因为'网络连接性'检查失败。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsLabel": "索引限制", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.initialVaidationLabel": "初始验证", "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityFailureMessage": "无法建立网络连接,因为'初始验证'检查失败。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityLabel": "网络连接性", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.submitButtonLabel": "添加域", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.testUrlButtonLabel": "在浏览器中测试 URL", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.unexpectedValidationErrorMessage": "意外错误", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlHelpText": "域 URL 需要协议,且不能包含任何路径。", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlLabel": "域 URL", - "xpack.enterpriseSearch.appSearch.crawler.addDomainForm.validateButtonLabel": "验证域", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel": "自动爬网", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix": "每", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.formDescription": "不用担心,我们将为您开始爬网。{readMoreMessage}。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.readMoreLink": "阅读更多内容。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleDescription": "爬网计划适用此引擎上的每个域。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleFrequencyLabel": "计划频率", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleUnitsLabel": "计划时间单位", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage": "自动爬网已禁用。", - "xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage": "您的自动爬网计划已更新。", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "在 {domainCount, plural, other {# 个域}}上进行 {crawlType} 爬网", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlDepthLabel": "最大爬网深度", - "xpack.enterpriseSearch.appSearch.crawler.components.crawlDetailsSummary.crawlTypeLabel": "爬网类型", - "xpack.enterpriseSearch.appSearch.crawler.configurationDocumentationLinkDescription": "详细了解如何在 Kibana 中配置网络爬虫", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.customEntryPointUrlsTextboxLabel": "定制入口点 URL", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.customSitemapUrlsTextboxLabel": "定制站点地图 URL", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.domainsAccordionButtonLabel": "添加域到您的爬网", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.emptyDomainsMessage": "请选择域。", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.entryPointsTabLabel": "入口点", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeaderDescription": "使用定制设置设置一次性爬网。", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeadTitle": "定制爬网配置", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "包括在 {robotsDotTxt} 中发现的站点地图", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldDescription": "设置最大爬网深度以指定网络爬虫应遍历的页面深度。将该值设置为一 (1) 可将爬网仅限定为入口点。", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldLabel": "最大爬网深度", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.seedUrlsAccordionButtonLabel": "种子 URL", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.selectedDescriptor": "已选定", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.sitemapsTabLabel": "站点地图", - "xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.startCrawlButtonLabel": "立即应用并爬网", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.previewTabLabel": "预览", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.rawJSONTabLabel": "原始 JSON", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.title": "爬网请求详情", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.domainsTitle": "域", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.seedUrlsTitle": "种子 URL", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.sitemapUrlsTitle": "站点地图 URL", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.avgResponseTimeLabel": "平均响应", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.clientErrorsLabel": "4xx 错误", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.durationTooltipTitle": "持续时间", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.logsDisabledMessage": "在设置中启用网络爬虫日志以获取更详细的爬网统计信息。", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesTooltip": "在爬网期间访问并提取的 URL。", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesTooltipTitle": "访问的页面", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesVisitedTooltipTitle": "页面", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.serverErrorsLabel": "5xx 错误", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.urlsTooltip": "网络爬虫在爬网期间发现的 URL,包括那些由于爬网配置未跟踪的 URL。", - "xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.urlsTooltipTitle": "看到的 URL", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusBanner.changesCalloutTitle": "所做的更改不会立即生效,直到下一次爬网开始。", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.cancelCrawlMenuItemLabel": "取消爬网", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.crawlingButtonLabel": "正在爬网.....", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.pendingButtonLabel": "待处理......", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.retryCrawlButtonLabel": "重试爬网", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.showSelectedFieldsButtonLabel": "仅显示选定字段", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startACrawlButtonLabel": "开始爬网", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startingButtonLabel": "正在启动......", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.stoppingButtonLabel": "正在停止......", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceled": "已取消", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceling": "正在取消", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.failed": "失败", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.pending": "待处理", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.running": "正在运行", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.skipped": "已跳过", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.starting": "正在启动", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.success": "成功", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspended": "已挂起", - "xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspending": "正在挂起", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsDescription": "此处记录了最近的爬网请求。使用每次爬网的请求 ID,可以在 Kibana 的 Discover 或 Logs 用户界面中跟踪进度并检查爬网事件。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.crawlType": "爬网类型", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.created": "创建时间", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domains": "域", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domainURL": "请求 ID", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.status": "状态", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.body": "您尚未开始任何爬网。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.title": "最近没有爬网请求", - "xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTitle": "最近爬网请求", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.beginsWithLabel": "开始于", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.containsLabel": "Contains", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.endsWithLabel": "结束于", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.regexLabel": "Regex", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.allowLabel": "允许", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.disallowLabel": "不允许", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.addButtonLabel": "添加爬网规则", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.deleteSuccessToastMessage": "爬网规则已删除。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.description": "创建爬网规则以包括或排除 URL 匹配规则的页面。规则按顺序运行,每个 URL 根据第一个匹配进行评估。{link}", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.descriptionLinkText": "详细了解爬网规则", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTableHead": "路径模式", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTooltip": "路径模式为文本字符串,但星号 (*) 字符除外,它是将匹配任何内容的元字符。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.policyTableHead": "策略", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.regexPathPatternTooltip": "路径模式是与 Ruby 语言正则表达式引擎兼容的正则表达式。", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.ruleTableHead": "规则", - "xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.title": "爬网规则", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.modalHeaderTitle": "爬网选定域", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.selectedDescriptor": "已选定", - "xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.startCrawlButtonLabel": "立即应用并爬网", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.full": "实线", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.partial": "部分", - "xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.reAppliedCrawlRules": "已重新应用爬网规则", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.allFieldsLabel": "所有字段", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.description": "网络爬虫仅索引唯一的页面。选择网络爬虫在考虑哪些网页重复时应使用的字段。取消选择所有架构字段以在此域上允许重复的文档。{documentationLink}。", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.learnMoreMessage": "详细了解内容哈希", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.resetToDefaultsButtonLabel": "重置为默认值", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.selectedFieldsLabel": "选定字段", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.showAllFieldsButtonLabel": "显示所有字段", - "xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.title": "重复文档处理", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.cannotUndoMessage": "这不能撤消", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.deleteDomainButtonLabel": "删除域", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.description": "从您的网络爬虫移除此域。这还将您已设置的所有入口点和爬网规则。{cannotUndoMessage}。", - "xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.title": "删除域", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.buttonLabel": "删除此域", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.manage.buttonLabel": "管理此域", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.actions": "操作", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.documents": "文档", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.domainURL": "域 URL", - "xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.lastActivity": "上次活动", - "xpack.enterpriseSearch.appSearch.crawler.domainsTitle": "域", - "xpack.enterpriseSearch.appSearch.crawler.empty.crawlerDocumentationLinkDescription": "详细了解网络爬虫", - "xpack.enterpriseSearch.appSearch.crawler.empty.description": "轻松索引您的网站内容。要开始,请输入您的域名,提供可选入口点和爬网规则,然后我们将处理剩下的事情。", - "xpack.enterpriseSearch.appSearch.crawler.empty.title": "添加域以开始", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.addButtonLabel": "添加入口点", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.description": "在此加入您的网站最重要的 URL。入口点 URL 将是要为其他页面的链接索引和处理的首批页面。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageDescription": "{link}以指定网络爬虫的入口点", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageLinkText": "添加入口点", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageTitle": "当前没有入口点。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.lastItemMessage": "网络爬虫需要至少一个入口点。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText": "详细了解入口点。", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.title": "入口点", - "xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.urlTableHead": "URL", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingButtonLabel": "自动爬网", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingTitle": "自动爬网", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.manageCrawlsButtonLabel": "管理爬网", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRules.successMessage": "正在后台重新应用爬网规则", - "xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRulesButtonLabel": "重新应用爬网规则", "xpack.enterpriseSearch.appSearch.crawler.simplifiedSelectable.deselectAllButtonLabel": "取消全选", "xpack.enterpriseSearch.appSearch.crawler.simplifiedSelectable.selectAllButtonLabel": "全选", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.addButtonLabel": "添加站点地图", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.deleteSuccessToastMessage": "站点地图已删除。", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.description": "为此域上的网络爬虫指定站点地图。", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.emptyMessageTitle": "当前没有站点地图。", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.title": "站点地图", - "xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.urlTableHead": "URL", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlAllDomainsMenuLabel": "爬网此引擎上的所有域", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlCustomSettingsMenuLabel": "使用定制设置执行爬网", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlSelectDomainsMenuLabel": "爬网选定域", - "xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.startACrawlButtonLabel": "开始爬网", - "xpack.enterpriseSearch.appSearch.credentials.apiEndpoint": "终端", - "xpack.enterpriseSearch.appSearch.credentials.apiKeys": "API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.copied": "已复制", - "xpack.enterpriseSearch.appSearch.credentials.copyApiEndpoint": "将 API 终结点复制到剪贴板。", - "xpack.enterpriseSearch.appSearch.credentials.copyApiKey": "将 API 密钥复制到剪贴板", - "xpack.enterpriseSearch.appSearch.credentials.createKey": "创建密钥", - "xpack.enterpriseSearch.appSearch.credentials.deleteKey": "删除 API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.documentationLink1": "访问文档", - "xpack.enterpriseSearch.appSearch.credentials.documentationLink2": "以了解有关密钥的更多信息。", - "xpack.enterpriseSearch.appSearch.credentials.editKey": "编辑 API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.empty.body": "允许应用程序代表您访问 Elastic App Search。", - "xpack.enterpriseSearch.appSearch.credentials.empty.buttonLabel": "了解 API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.empty.title": "创建您的首个 API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.flyout.createTitle": "创建新密钥", - "xpack.enterpriseSearch.appSearch.credentials.flyout.updateTitle": "更新 {tokenName}", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.helpText": "密钥可以访问的引擎:", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.label": "选择引擎", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.helpText": "访问所有当前和未来的引擎。", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.label": "完全的引擎访问", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.label": "引擎访问控制", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.helpText": "将密钥访问限定于特定引擎。", - "xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.label": "有限的引擎访问", - "xpack.enterpriseSearch.appSearch.credentials.formName.helpText": "您的密钥将命名为:{name}", - "xpack.enterpriseSearch.appSearch.credentials.formName.label": "密钥名称", - "xpack.enterpriseSearch.appSearch.credentials.formName.placeholder": "即 my-engine-key", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.helpText": "仅适用于私有 API 密钥。", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.label": "读写访问级别", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.readLabel": "读取权限", - "xpack.enterpriseSearch.appSearch.credentials.formReadWrite.writeLabel": "写入权限", - "xpack.enterpriseSearch.appSearch.credentials.formType.label": "密钥类型", - "xpack.enterpriseSearch.appSearch.credentials.hideApiKey": "隐藏 API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.list.enginesTitle": "引擎", - "xpack.enterpriseSearch.appSearch.credentials.list.keyTitle": "钥匙", - "xpack.enterpriseSearch.appSearch.credentials.list.modesTitle": "模式", - "xpack.enterpriseSearch.appSearch.credentials.list.nameTitle": "名称", - "xpack.enterpriseSearch.appSearch.credentials.list.typeTitle": "类型", - "xpack.enterpriseSearch.appSearch.credentials.showApiKey": "显示 API 密钥", - "xpack.enterpriseSearch.appSearch.credentials.title": "凭据", - "xpack.enterpriseSearch.appSearch.credentials.updateWarning": "现有 API 密钥可在用户之间共享。更改此密钥的权限将影响有权访问此密钥的所有用户。", - "xpack.enterpriseSearch.appSearch.credentials.updateWarningTitle": "谨慎操作!", - "xpack.enterpriseSearch.appSearch.cta": "浏览", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonDescription": "为此查询启用建议", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonLabel": "允许", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.allowQuerySuccessMessage": "您将收到有关此查询的未来建议的通知", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.description": "您将不会收到有关这些查询的建议的通知", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.queryColumnName": "查询", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.refresh": "刷新", - "xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.title": "忽略的查询", - "xpack.enterpriseSearch.appSearch.curations.settings.acceptNewSuggesstionsSwitchLabel": "自动接受新建议", - "xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutDescription": "自适应相关性需要在您帐户上启用分析。", - "xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutTitle": "已禁用分析", - "xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsDescription": "App Search 将监测您引擎的分析并提出策展更改建议,以帮助您交付最相关的结果。可以接受、拒绝或修改每个建议。", - "xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsTitle": "策展由自适应相关性提供支持", - "xpack.enterpriseSearch.appSearch.curations.settings.enableautomaticCurationsSwitchLabel": "启用建议", - "xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTASubtitle": "升级到{platinumLicenseName}订阅以充分利用 Machine Learning。通过分析您引擎的分析,App Search 能够建议新的或已更新的策展。轻松帮助您的用户准确找到他们寻找的内容。立即开始免费试用。", - "xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTATitle": "介绍由自适应相关性提供支持的策展", - "xpack.enterpriseSearch.appSearch.curations.settings.manageAnalyticsButtonLabel": "管理分析", - "xpack.enterpriseSearch.appSearch.curations.settings.platinum": "白金级", - "xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION": "开发人员可以管理引擎的所有方面。", - "xpack.enterpriseSearch.appSearch.documentCreation.api.description": "{documentsApiLink} 可用于将新文档添加到您的引擎、更新文档、按 ID 检索文档以及删除文档。有各种{clientLibrariesLink}可帮助您入门。", - "xpack.enterpriseSearch.appSearch.documentCreation.api.example": "要了解如何使用 API,可以在下面通过命令行或客户端库试用示例请求。", - "xpack.enterpriseSearch.appSearch.documentCreation.api.title": "按 API 索引", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.apiDescription": "POST 到文档终端", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.apiTitle": "从 API 索引", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.crawlDescription": "自动索引 URL 的文档", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.crawlTitle": "使用网络爬虫", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterLink": "阅读文档", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterText": "希望了解有关索引文档的详情?", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateIllustrationAltText": "图示", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateTitle": "添加文档", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.jsonDescription": "通过粘贴或上传原始 JSON 来添加文档", - "xpack.enterpriseSearch.appSearch.documentCreation.buttons.jsonTitle": "粘贴或上传 JSON", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.button": "连接到索引", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.description": "现在您可以直接连接到现有 Elasticsearch 索引,以便可以通过 Enterprise Search Ul 搜索和调整其数据。{learnMoreLink}", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.link": "详细了解如何使用现有索引", - "xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.title": "连接 Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.documentCreation.errorsTitle": "出问题了。请解决这些错误,然后重试。", - "xpack.enterpriseSearch.appSearch.documentCreation.helperText": "有三种方法将文档发送到引擎进行索引。您可以粘贴或上传 JSON 文件、POST 到文档 API 终端,或者使用 Elastic 网络爬虫来自动索引 URL 中的文档。", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.pasteTabName": "粘贴", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.title": "粘贴或上传 JSON", - "xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.uploadTabName": "上传", - "xpack.enterpriseSearch.appSearch.documentCreation.largeFile": "您正在上传极大的文件。这可能会锁定您的浏览器,或需要很长时间才能处理完。如果可能,请尝试将您的数据拆分成多个较小的文件。", - "xpack.enterpriseSearch.appSearch.documentCreation.noFileFound": "未找到文件。", - "xpack.enterpriseSearch.appSearch.documentCreation.notValidJson": "文档内容必须是有效的 JSON 数组或对象。", - "xpack.enterpriseSearch.appSearch.documentCreation.noValidFile": "解析文件时出现问题。", - "xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.description": "粘贴一系列的 JSON 文档。确保 JSON 有效且每个文档对象小于 {maxDocumentByteSize} 字节。", - "xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.label": "在此处粘贴 JSON", - "xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title": "添加新文档", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.documentNotIndexed": "未索引此文档!", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.fixErrors": "修复错误", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.invalidDocuments": "{invalidDocuments, number} 个{invalidDocuments, plural, other {文档}}有错误......", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.newDocuments": "已添加 {newDocuments, number} 个{newDocuments, plural, other {文档}}。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.newSchemaFields": "已将 {newFields, number} 个{newFields, plural, other {字段}}添加到引擎的架构。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.noNewDocuments": "没有新文档。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.noNewSchemaFields": "没有新的架构字段。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.otherDocuments": "及 {documents, number} 个其他{documents, plural, other {文档}}。", - "xpack.enterpriseSearch.appSearch.documentCreation.showSummary.title": "索引摘要", - "xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.label": "如果有 .json 文档,请拖放或上传。确保 JSON 有效且每个文档对象小于 {maxDocumentByteSize} 字节。", - "xpack.enterpriseSearch.appSearch.documentCreation.warningsTitle": "警告!", - "xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete": "是否确定要删除此文档?", - "xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess": "您的文档已删除", - "xpack.enterpriseSearch.appSearch.documentDetail.fieldHeader": "字段", - "xpack.enterpriseSearch.appSearch.documentDetail.title": "文档:{documentId}", - "xpack.enterpriseSearch.appSearch.documentDetail.valueHeader": "值", - "xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout": "此引擎已附加到 {elasticsearchIndexName}。您可以在 Kibana 中修改此索引的数据。", - "xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout.title": "此引擎的数据由 Elasticsearch 管理。", - "xpack.enterpriseSearch.appSearch.documents.empty.description": "您可以使用 App Search 网络爬虫通过上传 JSON 或使用 API 索引文档。", - "xpack.enterpriseSearch.appSearch.documents.empty.title": "添加您的首批文档", - "xpack.enterpriseSearch.appSearch.documents.indexDocuments": "索引文档", - "xpack.enterpriseSearch.appSearch.documents.metaEngineCallout": "元引擎包含很多源引擎。访问您的源引擎以更改其文档。", - "xpack.enterpriseSearch.appSearch.documents.metaEngineCallout.title": "您在元引擎中。", - "xpack.enterpriseSearch.appSearch.documents.paging.ariaLabelBottom": "搜索结果在结果底部分页", - "xpack.enterpriseSearch.appSearch.documents.paging.ariaLabelTop": "搜索结果在结果顶部分页", - "xpack.enterpriseSearch.appSearch.documents.search.ariaLabel": "筛选文档", - "xpack.enterpriseSearch.appSearch.documents.search.customizationButton": "定制筛选并排序", - "xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.button": "定制", - "xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.message": "是否知道您可以定制您的文档搜索体验?在下面单击'定制'以开始使用。", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFields": "呈现为筛选且可用作查询优化的分面值", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFieldsLabel": "筛选字段", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.sortFields": "用于显示结果排序选项,升序和降序", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.sortFieldsLabel": "排序字段", - "xpack.enterpriseSearch.appSearch.documents.search.customizationModal.title": "定制文档搜索", - "xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.noValue.selectOption": "", - "xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.showMore": "显示更多", - "xpack.enterpriseSearch.appSearch.documents.search.placeholder": "筛选文档......", - "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel": "每页要显示的结果数", - "xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.show": "显示:", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy": "排序依据", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.ariaLabel": "结果排序方式", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.ascendingDropDownOptionLabel": "{fieldName}(升序)", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.descendingDropDownOptionLabel": "{fieldName}(降序)", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.documentId": "文档 ID", - "xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.relevance": "相关性", - "xpack.enterpriseSearch.appSearch.documents.title": "文档", - "xpack.enterpriseSearch.appSearch.editorRoleTypeDescription": "编辑人员可以管理搜索设置。", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateButton": "管理索引", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateIllustrationAltText": "图示", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateTitle": "添加文档到您的索引", - "xpack.enterpriseSearch.appSearch.elasticsearchEngine.helperText": "您的 Elasticsearch 索引 {elasticsearchIndexName} 尚没有任何文档。在 Kibana 中打开索引管理以更改您的 Elasticsearch 索引。", - "xpack.enterpriseSearch.appSearch.emptyState.createFirstEngineCta": "创建引擎", - "xpack.enterpriseSearch.appSearch.emptyState.description1": "App Search 引擎存储文档以提升您的搜索体验。", - "xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.description": "请联系您的 App Search 管理员,为您创建或授予引擎的访问权限。", - "xpack.enterpriseSearch.appSearch.emptyState.nonAdmin.title": "没有引擎可用", - "xpack.enterpriseSearch.appSearch.emptyState.title": "创建您的首个引擎", - "xpack.enterpriseSearch.appSearch.engine.analytics.allTagsDropDownOptionLabel": "所有分析标签", - "xpack.enterpriseSearch.appSearch.engine.analytics.clickTablesDescription": "发现哪个查询生成最多和最少的点击量。", - "xpack.enterpriseSearch.appSearch.engine.analytics.clickTablesTitle": "点击分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.applyButtonLabel": "应用筛选", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel": "按结束日期筛选", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel": "按开始日期筛选", - "xpack.enterpriseSearch.appSearch.engine.analytics.filters.tagAriaLabel": "按分析标签筛选\"", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.cardDescription": "{queryTitle} 的查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.chartTooltip": "每天查询数", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.tableDescription": "此查询导致最多点击量的文档。", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.tableTitle": "排名靠前点击", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title": "查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchButtonLabel": "查看详情", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchPlaceholder": "前往搜索词", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryTablesDescription": "深入了解最频繁的查询以及未返回结果的查询。", - "xpack.enterpriseSearch.appSearch.engine.analytics.queryTablesTitle": "查询分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesDescription": "了解现时正发生的查询。", - "xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesTitle": "最近查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.clicksColumn": "点击", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.editTooltip": "管理策展", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksDescription": "此查询未引起任何文档的点击。", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksTitle": "无点击", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesDescription": "在此期间未执行任何查询。", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesTitle": "没有要显示的查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesDescription": "查询按接收的样子显示在此处。", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesTitle": "没有最近查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.moreTagsBadge": "及另外 {moreTagsCount} 个", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.queriesColumn": "查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.resultsColumn": "结果", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsColumn": "分析标签", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsCountBadge": "{tagsCount, plural, other {# 个标签}}", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.termColumn": "搜索词", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.timeColumn": "时间", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAction": "查看", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAllButtonLabel": "查看全部", - "xpack.enterpriseSearch.appSearch.engine.analytics.table.viewTooltip": "查看查询分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.title": "分析", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoClicksTitle": "没有点击的排名靠前查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoResultsTitle": "没有结果的排名靠前查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesTitle": "排名靠前查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesWithClicksTitle": "有点击的排名靠前查询", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations": "API 操作总数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks": "总单击数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments": "总文档数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries": "查询总数", - "xpack.enterpriseSearch.appSearch.engine.analytics.totalQueriesNoResults": "没有结果的查询总数", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.detailsButtonLabel": "详情", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel": "查看 API 参考", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyDescription": "API 请求发生时,日志将实时更新。", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyTitle": "在过去 24 小时中没有任何 API 事件", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.endpointTableHeading": "终端", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.flyout.title": "请求详情", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTableHeading": "方法", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTitle": "方法", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorDescription": "请检查您的连接或手动重新加载页面。", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorMessage": "无法刷新 API 日志数据", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.recent": "最近的 API 事件", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.requestBodyTitle": "请求正文", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.requestPathTitle": "请求路径", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.responseBodyTitle": "响应正文", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTableHeading": "状态", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTitle": "状态", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.timestampTitle": "时间戳", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.timeTableHeading": "时间", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.title": "API 日志", - "xpack.enterpriseSearch.appSearch.engine.apiLogs.userAgentTitle": "用户代理", - "xpack.enterpriseSearch.appSearch.engine.crawler.title": "网络爬虫", - "xpack.enterpriseSearch.appSearch.engine.curation.automatedLabel": "自动", - "xpack.enterpriseSearch.appSearch.engine.curation.convertToManualCurationButtonLabel": "转换到手动策展", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyButtonLabel": "历史记录", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription": "由自适应相关性提供支持的策展的最近更改详细日志。", - "xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle": "自适应相关性更改", - "xpack.enterpriseSearch.appSearch.engine.curation.suggestedDocumentsCallout.description": "基于您引擎的分析,新的建议文档提升已准备就绪,可供复查。", - "xpack.enterpriseSearch.appSearch.engine.curation.suggestedDocumentsCallout.title": "此查询的新建议文档", - "xpack.enterpriseSearch.appSearch.engine.curations.actionsPopoverAriaLabel": "更多建议操作", - "xpack.enterpriseSearch.appSearch.engine.curations.activeQueryHelpText": "选择查询以查看它们的有机搜索结果", - "xpack.enterpriseSearch.appSearch.engine.curations.activeQueryLabel": "活动查询", - "xpack.enterpriseSearch.appSearch.engine.curations.addQueryButtonLabel": "添加查询", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel": "手动添加结果", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchEmptyDescription": "未找到匹配内容。", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder": "搜索引擎文档", - "xpack.enterpriseSearch.appSearch.engine.curations.addResult.title": "将结果添加到策展", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.queryColumnHeader": "查询", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableDecription": "由自适应相关性提供支持的策展的最近更改详细日志。", - "xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableTitle": "自适应相关性更改", - "xpack.enterpriseSearch.appSearch.engine.curations.convertToManualCurationConfirmation": "是否确定要将此策展转换为手动策展?", - "xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesDescription": "添加要策展的一个或多个查询。之后将能够添加或删除更多查询。", - "xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesTitle": "策展查询", - "xpack.enterpriseSearch.appSearch.engine.curations.create.title": "创建策展", - "xpack.enterpriseSearch.appSearch.engine.curations.deleteConfirmation": "确定要移除此策展?", - "xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage": "您的策展已删除", - "xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel": "降低此结果", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel": "阅读策展指南", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.description": "使用策展提升和隐藏文档。帮助人们发现最想让他们发现的内容。", - "xpack.enterpriseSearch.appSearch.engine.curations.empty.noCurationsTitle": "创建您的首个策展", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription": "通过单击上面有机结果上的眼睛图标,可隐藏文档,或手动搜索和隐藏结果。", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle": "您尚未隐藏任何文档", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel": "全部取消隐藏", - "xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title": "隐藏结果", - "xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel": "隐藏此结果", - "xpack.enterpriseSearch.appSearch.engine.curations.historyPageTabLabel": "历史记录", - "xpack.enterpriseSearch.appSearch.engine.curations.manage.title": "管理策展", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel": "管理查询", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryDescription": "编辑、添加或移除此策展的查询。", - "xpack.enterpriseSearch.appSearch.engine.curations.manageQueryTitle": "管理查询", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.description": "没有要显示的有机结果。{manualDescription}", - "xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.manualDescription": "在上面添加或更改活动查询。", - "xpack.enterpriseSearch.appSearch.engine.curations.overview.title": "已策展结果", - "xpack.enterpriseSearch.appSearch.engine.curations.overviewPageTabLabel": "概览", - "xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel": "提升此结果", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.automatedEmptyDescription": "我们尚未确定任何要提升的文档", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.emptyDescription": "使用星号标记来自下面有机结果的文档或手动搜索或提升结果。", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.managedByAppSearchDescription": "此策展正由 App Search 自动执行", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel": "全部降低", - "xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title": "已提升结果", - "xpack.enterpriseSearch.appSearch.engine.curations.queryPlaceholder": "输入查询", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.queryColumnHeader": "查询", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableDescription": "查看您之前拒绝的建议。", - "xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableTitle": "最近拒绝的建议", - "xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation": "确定要清除更改并返回到默认结果?", - "xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription": "通过单击星号提升结果,通过单击眼睛隐藏结果。", - "xpack.enterpriseSearch.appSearch.engine.curations.settingsPageTabLabel": "设置", - "xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel": "显示此结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.acceptButtonLabel": "接受", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAcceptButtonLabel": "接受此建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsAutomateButtonLabel": "自动化 - 始终接受此查询的新建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsPopoverTitle": "管理建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsRejectButtonLabel": "拒绝此建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsTurnOffButtonLabel": "拒绝并关闭此查询的建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.breadcrumbLabel": "已建议:{query}", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.collapseButtonLabel": "折叠有机搜索结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.currentTitle": "当前", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.expandButtonLabel": "展开有机搜索结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsDescription": "未返回此查询的任何有机搜索结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsTitle": "无结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noResultsMessage": "此查询当前没有任何已提升的文档", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.notFoundError": "找不到建议,可能已应用或拒绝建议。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.rejectButtonLabel": "拒绝", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelDescription": "此策展可以由 App Search 自动执行", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelTitle": "已提升结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAppliedMessage": "已成功应用建议。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyRejectedMessage": "已成功拒绝建议。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.suggestionTitle": "已建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.title": "管理建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.hideForNowLabel": "暂时隐藏此项", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.reviewSuggestionsButtonLabel": "复查建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.lastUpdatedTableHeader": "上次更新时间", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.promotedDocumentsTableHeader": "已提升结果", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.queryTableHeader": "查询", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.description": "根据您的分析,可以通过提升某些文档来改进以下查询的结果。", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.overridesLabel": "覆盖", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.title": "{totalSuggestions} 个建议", - "xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.viewTooltip": "查看建议", - "xpack.enterpriseSearch.appSearch.engine.curations.table.automatedLabel": "自动", - "xpack.enterpriseSearch.appSearch.engine.curations.table.column.lastUpdated": "上次更新时间", - "xpack.enterpriseSearch.appSearch.engine.curations.table.column.queries": "查询", - "xpack.enterpriseSearch.appSearch.engine.curations.table.deleteTooltip": "删除策展", - "xpack.enterpriseSearch.appSearch.engine.curations.table.editTooltip": "编辑策展", - "xpack.enterpriseSearch.appSearch.engine.curations.table.newSuggestionLabel": "新建议", - "xpack.enterpriseSearch.appSearch.engine.curations.table.title": "活动策展", - "xpack.enterpriseSearch.appSearch.engine.curations.title": "策展", - "xpack.enterpriseSearch.appSearch.engine.documents.empty.buttonLabel": "阅读文档指南", - "xpack.enterpriseSearch.appSearch.engine.elasticsearchEngineBadge": "ELASTICSEARCH 索引", - "xpack.enterpriseSearch.appSearch.engine.metaEngineBadge": "元引擎", - "xpack.enterpriseSearch.appSearch.engine.overview.analyticsLink": "查看分析", - "xpack.enterpriseSearch.appSearch.engine.overview.apiLogsLink": "查看 API 日志", - "xpack.enterpriseSearch.appSearch.engine.overview.chartDuration": "过去 7 天", - "xpack.enterpriseSearch.appSearch.engine.overview.empty.heading": "引擎设置", - "xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction": "查看文档", - "xpack.enterpriseSearch.appSearch.engine.overview.heading": "引擎概览", - "xpack.enterpriseSearch.appSearch.engine.overview.title": "概览", - "xpack.enterpriseSearch.appSearch.engine.pollingErrorDescription": "请检查您的连接或手动重新加载页面。", - "xpack.enterpriseSearch.appSearch.engine.pollingErrorMessage": "无法获取引擎数据", - "xpack.enterpriseSearch.appSearch.engine.queryTester.searchPlaceholder": "搜索引擎文档", - "xpack.enterpriseSearch.appSearch.engine.queryTesterTitle": "查询测试器", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addBoostDropDownOptionLabel": "添加权重提升", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addOperationDropDownOptionLabel": "添加", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.deleteBoostButtonLabel": "删除权重提升", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.exponentialFunctionDropDownOptionLabel": "指数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.functionalDropDownOptionLabel": "函数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.funtional.functionDropDownLabel": "函数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.funtional.operationDropDownLabel": "操作", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.gaussianFunctionDropDownOptionLabel": "高斯", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.impactLabel": "影响", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.linearFunctionDropDownOptionLabel": "线性", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.logarithmicBoostFunctionDropDownOptionLabel": "对数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.multiplyOperationDropDownOptionLabel": "乘积", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximity.centerLabel": "居中", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximity.functionDropDownLabel": "函数", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximityDropDownOptionLabel": "邻近度", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.title": "权重提升", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.valueDropDownOptionLabel": "值", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description": "管理引擎的精确性和相关性设置", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFields.title": "已禁用字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFieldsExplanationMessage": "由于字段类型冲突,为非活动", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.elasticsearch.description": "管理引擎的相关性设置", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel": "阅读相关性调整指南", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.description": "您索引一些文档后,系统便会自动为您创建架构。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.title": "添加文档以调整相关性", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoosts": "无效的提权", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsBannerLabel": "您有无效的权重提升!", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsErrorMessage": "您的一个或多个权重提升不再有效,可能因为架构类型更改。删除任何旧的或无效的权重提升以关闭此告警。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder": "筛选 {schemaFieldsLength} 个字段......", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.descriptionLabel": "搜索此字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.rowLabel": "文本搜索", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.textSearch.warningLabel": "仅可以对文本字段启用搜索", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title": "管理字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.weight.label": "权重", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteConfirmation": "是否确定要删除此权重提升?", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteSuccess": "相关性已重置为默认值", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.resetConfirmation": "确定要还原相关性默认值?", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.successDescription": "更改将短暂地影响您的结果。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.updateSuccess": "相关性已调整", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.ariaLabel": "查全率与精确性", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.description": "在您的引擎上微调精确性与查全率设置。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link": "了解详情。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.precision.label": "精确度", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.recall.label": "查全率", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step01.description": "最高查全率、最低精确性设置。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step02.description": "默认值:必须匹配不到一半的字词。完全错别字容差已应用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step03.description": "已增加字词要求:要匹配,文档必须包含最多具有 2 个字词的查询的所有字词,如果查询具有更多字词,则包含一半。完全错别字容差已应用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step04.description": "已增加字词要求:要匹配,文档必须包含最多具有 3 个字词的查询的所有字词,如果查询具有更多字词,则包含四分之三。完全错别字容差已应用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step05.description": "已增加字词要求:要匹配,文档必须包含最多具有 4 个字词的查询的所有字词,如果查询具有更多字词,则只包含一个。完全错别字容差已应用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step06.description": "已增加字词要求:要匹配,文档必须包含任何查询的所有字词。完全错别字容差已应用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step07.description": "最严格的字词要求:要匹配,文档必须包含相同字段中的所有字词。完全错别字容差已应用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step08.description": "最严格的字词要求:要匹配,文档必须包含相同字段中的所有字词。部分错别字容差已应用:模糊匹配已禁用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step09.description": "最严格的字词要求:要匹配,文档必须包含相同字段中的所有字词。部分错别字容差已应用:模糊匹配和前缀已禁用。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step10.description": "最严格的字词要求:要匹配,文档必须包含相同字段中的所有字词。部分错别字容差已应用:除了上述,也未更正缩写和连字。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step11.description": "仅完全匹配将应用,仅容忍首字母大小写的差别。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.title": "精确性调整", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.enterQueryMessage": "输入查询以查看搜索结果", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.noResultsMessage": "未找到匹配内容", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.searchPlaceholder": "搜索 {engineName}", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title": "预览", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaConflictsBannerLabel": "已禁用字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaConflictsErrorMessage": "{schemaFieldsWithConflictsCount, number} 个非活动{schemaFieldsWithConflictsCount, plural, other {字段}},字段类型冲突。{link}", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaFieldsLinkLabel": "架构字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title": "相关性调整", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.uncofirmedFieldsBannerLabel": "默认不搜索最近添加的字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.uncofirmedFieldsErrorMessage": "如果这些新字段应可搜索,请在此处通过切换文本搜索打开它们。否则,确认您的新 {schemaLink} 以关闭此告警。", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.unsearchedFields": "未搜索的字段", - "xpack.enterpriseSearch.appSearch.engine.relevanceTuning.whatsThisLinkLabel": "这是什么?", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.clearButtonLabel": "清除所有值", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmResetMessage": "确定要还原结果设置默认值?这会将所有字段重置到没有限制的原始值。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmSaveMessage": "更改将立即启动。确保您的应用程序已可接受新的搜索结果!", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.buttonLabel": "阅读结果设置指南", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.description": "您索引一些文档后,系统便会自动为您创建架构。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.title": "添加文档以调整设置", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.fieldTypeConflictText": "字段类型冲突", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.numberFieldPlaceholder": "无限制", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.pageDescription": "扩充搜索结果并选择将显示的字段。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.delayedValue": "延迟", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.goodValue": "良好", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.optimalValue": "最优", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.standardValue": "标准", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformanceLabel": "查询性能:{performanceValue}", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.errorMessage": "发生错误。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.inputPlaceholder": "键入搜索查询以测试响应......", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.noResultsMessage": "无结果。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponseTitle": "样本响应", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage": "结果设置已保存", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.disabledFieldsTitle": "已禁用字段", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.fallbackTitle": "回退", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle": "最大大小", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.nonTextFieldsTitle": "非文本字段", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle": "原始", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.snippetTitle": "代码片段", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.textFieldsTitle": "文本字段", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTitle": "高亮显示", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTooltip": "代码片段是字段值的转义表示。查询匹配封装在 标记中以突出显示。回退将寻找代码片段匹配,但如果未找到任何内容,将回退到转义的原始值。范围是介于 20-1000 之间。默认为 100。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawAriaLabel": "切换原始字段", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTitle": "原始", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTooltip": "原始字段是字段值的确切表示。不得少于 20 个字符。默认为整个字段。", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetAriaLabel": "切换文本代码片段", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetFallbackAriaLabel": "切换代码片段回退", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.title": "结果设置", - "xpack.enterpriseSearch.appSearch.engine.resultSettings.unsavedChangesMessage": "结果设置尚未保存。是否确定要离开?", - "xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge": "样本引擎", - "xpack.enterpriseSearch.appSearch.engine.schema.addSchemaErrorMessage": "字段名称已存在:{fieldName}", - "xpack.enterpriseSearch.appSearch.engine.schema.addSchemaSuccessMessage": "已添加新字段:{fieldName}", - "xpack.enterpriseSearch.appSearch.engine.schema.confirmSchemaButtonLabel": "确认类型", - "xpack.enterpriseSearch.appSearch.engine.schema.conflicts": "架构冲突", - "xpack.enterpriseSearch.appSearch.engine.schema.createSchemaFieldButtonLabel": "创建架构字段", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel": "阅读索引架构指南", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.description": "提前创建架构字段,或索引一些文档,系统将为您创建架构。", - "xpack.enterpriseSearch.appSearch.engine.schema.empty.title": "创建架构", - "xpack.enterpriseSearch.appSearch.engine.schema.errors": "架构更改错误", - "xpack.enterpriseSearch.appSearch.engine.schema.hasIncompleteFields": "未在所有字段上启用精确性调整", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.description": "某些字段缺少一个或多个供 App Search 使用的子字段。如不添加这些子字段,某些搜索功能可能无效。", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.link": "了解详情。", - "xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.title": "{count, plural, one {一个字段} other {# 个字段}}缺少子字段", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsDescription": "属于一个或多个引擎的字段。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsTitle": "活动字段", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.allEngines": "全部", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutDescription": "在组成此元引擎的源引擎上字段有不一致的字段类型。应用源引擎中一致的字段类型,以使这些字段可搜索。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutTitle": "{conflictingFieldsCount, plural, other {# 个字段}}不可搜索", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.description": "活动和非活动字段,按引擎。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.fieldTypeConflicts": "字段类型冲突", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsDescription": "这些字段有类型冲突。要激活这些字段,更改源引擎中要匹配的类型。", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsTitle": "非活动字段", - "xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.title": "元引擎架构", - "xpack.enterpriseSearch.appSearch.engine.schema.missingSubfieldsLabel": "缺少子字段", - "xpack.enterpriseSearch.appSearch.engine.schema.pageDescription": "添加新字段或更改现有字段的类型。", - "xpack.enterpriseSearch.appSearch.engine.schema.pageTitle": "管理引擎架构", - "xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageDescription": "查看架构字段类型。", - "xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageTitle": "引擎架构", - "xpack.enterpriseSearch.appSearch.engine.schema.reindexErrorsBreadcrumb": "重新索引错误", - "xpack.enterpriseSearch.appSearch.engine.schema.reindexJob.title": "架构更改错误", - "xpack.enterpriseSearch.appSearch.engine.schema.title": "架构", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFieldLabel": "最近添加", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields": "新的未确认字段", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.description": "将新的架构字段设置为正确或预期类型,然后确认字段类型。", - "xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.title": "您最近添加了新的架构字段", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.description": "如果这些新字段应可搜索,则请更新搜索设置,以包括它们。如果您希望它们仍保持不可搜索,请确认新的字段类型以忽略此告警。", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.searchSettingsButtonLabel": "更新搜索设置", - "xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.title": "默认不搜索最近添加的字段", - "xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaButtonLabel": "保存更改", - "xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaSuccessMessage": "架构已更新", - "xpack.enterpriseSearch.appSearch.engine.searchUI.bodyDescription": "搜索 UI 是免费且开放的库,用于使用 React 构建搜索体验。{link}。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel": "阅读搜索 UI 指南", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.description": "您索引一些文档后,系统便会自动为您创建架构。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.empty.title": "添加文档以生成搜索 UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldHelpText": "呈现为筛选且可用作查询优化的分面值", - "xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldLabel": "筛选字段(可选)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.generatePreviewButtonLabel": "生成搜索体验", - "xpack.enterpriseSearch.appSearch.engine.searchUI.guideLinkText": "详细了解搜索 UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.lowerBodyDescription": "使用下面的字段生成使用搜索 UI 构建的搜索体验示例。使用该示例预览搜索结果或基于该示例创建自己的定制搜索体验。{link}。", - "xpack.enterpriseSearch.appSearch.engine.searchUI.repositoryLinkText": "查看 Github 存储库", - "xpack.enterpriseSearch.appSearch.engine.searchUI.sortFieldLabel": "排序字段(可选)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.sortHelpText": "用于显示结果排序选项,升序和降序", - "xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldHelpText": "提供图像 URL 以显示缩略图图像", - "xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldLabel": "缩略图字段(可选)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.title": "搜索 UI", - "xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldHelpText": "用作每个已呈现结果的顶层可视标识符", - "xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldLabel": "标题字段(可选)", - "xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldHelpText": "在适用时用作结果的链接目标", - "xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldLabel": "URL 字段(可选)", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesButtonLabel": "添加引擎", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.description": "将其他引擎添加到此元引擎", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.title": "添加引擎", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesPlaceholder": "选择引擎", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesSuccessMessage": "{sourceEnginesCount, plural, other {# 个引擎已}}添加到此元引擎", - "xpack.enterpriseSearch.appSearch.engine.souceEngines.title": "管理引擎", - "xpack.enterpriseSearch.appSearch.engine.suggestedCurationsCallout.description": "基于您引擎的分析,新的建议策展已准备就绪,可供复查。", - "xpack.enterpriseSearch.appSearch.engine.suggestedCurationsCallout.title": "要复查的新建议策展", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSuccessMessage": "同义词集已创建", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetButtonLabel": "创建同义词集", - "xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetTitle": "添加同义词集", - "xpack.enterpriseSearch.appSearch.engine.synonyms.deleteConfirmationMessage": "是否确定要删除此同义词集?", - "xpack.enterpriseSearch.appSearch.engine.synonyms.deleteSuccessMessage": "同义词集已删除", - "xpack.enterpriseSearch.appSearch.engine.synonyms.description": "使用同义词将数据集中有相同上下文意思的查询关联在一起。", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel": "阅读同义词指南", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.description": "同义词将具有类似上下文或意思的查询关联在一起。使用它们将用户导向相关内容。", - "xpack.enterpriseSearch.appSearch.engine.synonyms.empty.title": "创建您的首个同义词集", - "xpack.enterpriseSearch.appSearch.engine.synonyms.iconAriaLabel": "同义词", - "xpack.enterpriseSearch.appSearch.engine.synonyms.impactDescription": "此集合将短暂地影响您的结果。", - "xpack.enterpriseSearch.appSearch.engine.synonyms.synonymInputPlaceholder": "输入同义词", - "xpack.enterpriseSearch.appSearch.engine.synonyms.title": "同义词", - "xpack.enterpriseSearch.appSearch.engine.synonyms.updateSuccessMessage": "同义词集已更新", - "xpack.enterpriseSearch.appSearch.engine.synonyms.updateSynonymSetTitle": "管理同义词集", - "xpack.enterpriseSearch.appSearch.engine.universalLanguage": "通用", - "xpack.enterpriseSearch.appSearch.engineAssignmentLabel": "引擎分配", - "xpack.enterpriseSearch.appSearch.engineCreation.configureElasticsearchEngine.callout.title": "将创建别名并将其用于此引擎", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.errorText": "存在名称为 {aliasName} 的现有索引或别名。请选择其他别名名称。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.label": "别名名称", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefix.helpText": "别名名称必须以'search-'为前缀以用于 App Search 引擎", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefixAndNamed.helpText": "别名名称必须以'search-'为前缀以用于 App Search 引擎。将命名您的别名", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.appSearch.description": "为您的 App Search 引擎提供唯一名称和可选的语言选择。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.backButton.label": "返回", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.body": "App Search 引擎只能使用以'search-'为前缀的索引或别名来创建。如果选择不以'search-'开头的索引,将创建并使用该索引的别名。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.title": "App Search 具有索引和别名名称要求", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.continue.label": "继续", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.description": "请为您的 App Search 引擎提供唯一名称并选择索引。", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.docCount": "文档计数", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.indexSelectorAriaLabel": "选择您要使用的 Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.selectable.empty": "无 Elasticsearch 索引可用", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.selectable.loading": "正在加载 Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.status": "状态", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.storage": "存储大小", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.searchIndexSelectable.helpText": "选择以'search-'为前缀的索引或别名,或在下面创建新别名", - "xpack.enterpriseSearch.appSearch.engineCreation.configureForm.searchIndexSelectable.label": "选择您要使用的 Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.engineCreation.form.backButton.label": "返回", - "xpack.enterpriseSearch.appSearch.engineCreation.form.continue.label": "创建搜索引擎", - "xpack.enterpriseSearch.appSearch.engineCreation.form.editConfiguration.label": "编辑配置", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineLanguage.label": "引擎语言", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.allowedCharactersHelpText": "引擎名称只能包含小写字母、数字和连字符", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.label": "引擎名称", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.placeholder": "例如,my-search-engine", - "xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.sanitizedNameHelpText": "您的引擎将命名为", - "xpack.enterpriseSearch.appSearch.engineCreation.form.submitButton.buttonLabel": "创建引擎", - "xpack.enterpriseSearch.appSearch.engineCreation.form.title": "配置搜索引擎", - "xpack.enterpriseSearch.appSearch.engineCreation.nextStep.buttonLabel": "继续", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.aliasName.title": "别名名称", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.description": "将使用以下配置创建您的 App Search 引擎。", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.elasticsearchIndex.title": "Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineName.title": "引擎名称", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.description": "基于 Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.title": "引擎类型", - "xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.title.label": "复查搜索引擎", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.appSearch.description": "使用 App Search API 来管理您的文档。App Search 会将您的文档写入到底层索引,并为您管理该索引。", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.appSearch.title": "App Search 托管文档", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.elasticsearch.description": "使用现有索引来管理您的文档。通过 App Search 将搜索添加到 Elasticsearch 索引。某些函数需要特定子字段。", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngine.elasticsearch.title": "基于 Elasticsearch 索引", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description": "现在,您可以创建使用现有 Elasticsearch 索引的搜索引擎,将 App Search 的搜索管理工具与 Elasticsearch 索引的灵活性结合起来。", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description.link": "了解详情", - "xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.title": "选择搜索引擎类型", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label": "配置", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.finish.label": "完成", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.review.label": "复查", - "xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label": "搜索引擎类型", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.chineseDropDownOptionLabel": "中文", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.danishDropDownOptionLabel": "丹麦语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.dutchDropDownOptionLabel": "荷兰语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.englishDropDownOptionLabel": "英语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.frenchDropDownOptionLabel": "法语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.germanDropDownOptionLabel": "德语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.italianDropDownOptionLabel": "意大利语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.japaneseDropDownOptionLabel": "日语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.koreanDropDownOptionLabel": "朝鲜语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseBrazilDropDownOptionLabel": "葡萄牙语(巴西)", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseDropDownOptionLabel": "葡萄牙语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.russianDropDownOptionLabel": "俄语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.spanishDropDownOptionLabel": "西班牙语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.thaiDropDownOptionLabel": "泰语", - "xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.universalDropDownOptionLabel": "通用", - "xpack.enterpriseSearch.appSearch.engineCreation.title": "创建引擎", - "xpack.enterpriseSearch.appSearch.engineRequiredError": "至少需要分配一个引擎。", - "xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsButtonLabel": "刷新", - "xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsMessage": "已记录新事件。", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.closeButton": "关闭", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.eventTip": "正在显示过去 24 小时的事件", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.eventCategory": "事件类别", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.eventType": "事件类型", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.outcome": "结果", - "xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.updatedBy": "更新者", - "xpack.enterpriseSearch.appSearch.engines.createEngineButtonLabel": "创建引擎", - "xpack.enterpriseSearch.appSearch.engines.createMetaEngineButtonLabel": "创建元引擎", - "xpack.enterpriseSearch.appSearch.engines.curations.refreshButton": "刷新", - "xpack.enterpriseSearch.appSearch.engines.curations.rejectedCurationsHistoryPanel.refresh": "刷新", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptButtonLabel": "详细了解元引擎", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptDescription": "元引擎允许您将多个引擎组合成一个可搜索引擎。", - "xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptTitle": "创建您的首个元引擎", - "xpack.enterpriseSearch.appSearch.engines.metaEnginesTable.fieldTypeConflictWarning": "字段类型冲突", - "xpack.enterpriseSearch.appSearch.engines.metaEnginesTable.sourceEnginesCount": "{sourceEnginesCount, plural, other {# 个引擎}}", - "xpack.enterpriseSearch.appSearch.engines.title": "引擎", - "xpack.enterpriseSearch.appSearch.enginesOverview.metaEnginesTable.sourceEngines.title": "源引擎", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.buttonDescription": "删除此引擎", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage.buttonDescription": "管理此引擎", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions": "操作", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt": "创建于", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount": "文档计数", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount": "字段计数", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.language": "语言", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.lastUpdated": "上次更新时间", - "xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name": "名称", - "xpack.enterpriseSearch.appSearch.enginesOverview.title": "引擎概览", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.description": "提交反馈即表示您确认您已阅读并同意我们的{termsOfService},并且 Elastic 可以使用您提供的上述详细信息{contact},以向您介绍我们相关的产品和服务。请参阅{privacyStatementLink}了解更多详情,或随时退出。", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.Label": "是否要分享任何其他反馈?", - "xpack.enterpriseSearch.appSearch.gateForm.additionalFeedback.optionalLabel": "可选", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureButtonLabel": "添加搜索分析", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureDescription": "为搜索应用程序添加并查看分析和日志", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.featureName": "搜索分析和日志", - "xpack.enterpriseSearch.appSearch.gateForm.analyticsAndLogs.panelText": "您可以通过行为分析来跟踪并分析用户的搜索和单击行为。检测您的网站或应用程序,以跟踪相关用户操作。", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureButtonLabel": "通过 Elasticsearch 提供保护", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureDescription": "管理您的用户和角色,以及用于访问搜索终端的凭据", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.featureName": "凭据和角色", - "xpack.enterpriseSearch.appSearch.gateForm.credentials.panelText": "Elasticsearch 提供了一组全面的安全功能,包括文档级别安全性和基于角色的访问控制。", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureButtonLabel": "使用查询规则", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureDescription": "策展并固定特定查询的结果", - "xpack.enterpriseSearch.appSearch.gateForm.curations.featureName": "策展结果", - "xpack.enterpriseSearch.appSearch.gateForm.curations.panelText": "查询规则提供了一组更加可靠的工具,以便为匹配特定条件和元数据的查询定制搜索结果。", - "xpack.enterpriseSearch.appSearch.gateForm.description": "单机版 App Search 产品在维护模式下仍然可用,但不建议使用它来获得新的搜索体验。我们建议使用本机 Elasticsearch 工具,它们提供了灵活性和可组合性,并包括令人兴奋的全新搜索功能。为帮助选择最适合您用例的工具,我们创建了这个推荐向导。选择所需功能,我们将为您指出对应的 Elasticsearch 功能。如果仍然希望使用单机版 App Search 产品,您可以在提交表单后开始。", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.learnMore": "了解详情", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.subTitle": "根据您的选择,我们建议:", - "xpack.enterpriseSearch.appSearch.gateForm.educationalPanel.title": "Elasticsearch 本机等价项", - "xpack.enterpriseSearch.appSearch.gateForm.featureOther.Label": "是否可以说明您在寻找其他哪些功能?", - "xpack.enterpriseSearch.appSearch.gateForm.features.Label": "您寻求使用哪些 App Search 功能?", - "xpack.enterpriseSearch.appSearch.gateForm.features.selectOption": "选择选项", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label": "加入我们的用户研究调查以改进 Elasticsearch?", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.No": "否", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.Label.Yes": "是", - "xpack.enterpriseSearch.appSearch.gateForm.participateUxLab.optionalLabel": "可选", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureButtonLabel": "调整搜索相关度", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureDescription": "使用排名和提升方法调整结果的相关度", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.featureName": "相关性调整", - "xpack.enterpriseSearch.appSearch.gateForm.relevanceTuning.panelText": "Elasticsearch 的查询 DSL 提供了一组全面的相关性工具。", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureButtonLabel": "使用搜索 UI 构建搜索体验", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureDescription": "使用图形用户界面 (GUI) 来管理您的搜索应用程序体验", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.featureName": "搜索和管理 UI", - "xpack.enterpriseSearch.appSearch.gateForm.searchManagementUis.panelText": "搜索 UI 提供了构建现代搜索体验所需的组件。", - "xpack.enterpriseSearch.appSearch.gateForm.submit": "提交", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureButtonLabel": "使用同义词进行搜索", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureDescription": "通过基于同义词的查询扩展来执行搜索", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.featureName": "使用同义词进行搜索", - "xpack.enterpriseSearch.appSearch.gateForm.synonyms.panelText": "使用 Elasticsearch 同义词 API 轻松创建和管理同义词集。", - "xpack.enterpriseSearch.appSearch.gateForm.title": "开始前......", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureButtonLabel": "试用开放式网络爬虫", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureDescription": "使用网络爬虫将 Web 内容采集到 Elasticsearch", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.featureName": "网络爬虫", - "xpack.enterpriseSearch.appSearch.gateForm.webCrawler.panelText": "您是否知道,全新自管型 Elastic 开放式网络爬虫现已可用?您可以使 Web 内容与搜索优化的索引保持同步!", - "xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsDetail": "要管理分析和日志记录,请{visitSettingsLink}。", - "xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsLinkText": "访问您的设置", - "xpack.enterpriseSearch.appSearch.logRetention.callout.disabledSinceTitle": "自 {disabledDate}后,{logsTitle} 已禁用。", - "xpack.enterpriseSearch.appSearch.logRetention.callout.disabledTitle": "{logsTitle} 已禁用。", - "xpack.enterpriseSearch.appSearch.logRetention.customPolicy": "您有定制 {logsType} 日志保留策略。", - "xpack.enterpriseSearch.appSearch.logRetention.defaultPolicy": "您的 {logsType} 日志将存储至少 {minAgeDays, plural, other {# 天}}。", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging": "所有引擎的 {logsType} 日志记录均已禁用。", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging.collected": "{logsType} 日志的最后收集日期为 {disabledAtDate}。", - "xpack.enterpriseSearch.appSearch.logRetention.noLogging.notCollected": "未收集任何 {logsType} 日志。", - "xpack.enterpriseSearch.appSearch.logRetention.tooltip": "日志保留信息", - "xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.capitalized": "分析", - "xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.lowercase": "分析", - "xpack.enterpriseSearch.appSearch.logRetention.type.api.title.capitalized": "API", - "xpack.enterpriseSearch.appSearch.logRetention.type.api.title.lowercase": "API", - "xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.capitalized": "审计", - "xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.lowercase": "审计", - "xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.capitalized": "网络爬虫", - "xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.lowercase": "网络爬虫", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationDescription": "{documentationLink}以了解如何开始。", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationLink": "阅读文档", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.allowedCharactersHelpText": "元引擎名称只能包含小写字母、数字和连字符", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.label": "元引擎名称", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.placeholder": "例如 my-meta-engine", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.sanitizedNameHelpText": "您的元引擎将命名", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.metaEngineDescription": "元引擎允许您将多个引擎组合成一个可搜索引擎。", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.label": "将源引擎添加到此元引擎", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.maxSourceEnginesWarningTitle": "元引擎的源引擎数目限制为 {maxEnginesPerMetaEngine}", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.submitButton.buttonLabel": "创建元引擎", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.form.title": "命名您的元引擎", - "xpack.enterpriseSearch.appSearch.metaEngineCreation.title": "创建元引擎", - "xpack.enterpriseSearch.appSearch.metaEngines.title": "元引擎", - "xpack.enterpriseSearch.appSearch.multiInputRows.addValueButtonLabel": "添加值", - "xpack.enterpriseSearch.appSearch.multiInputRows.inputRowPlaceholder": "输入值", - "xpack.enterpriseSearch.appSearch.ownerRoleTypeDescription": "所有者可以执行任何操作。该帐户可以有很多所有者,但任何时候必须至少有一个所有者。", - "xpack.enterpriseSearch.appSearch.productCardDescription": "为应用和网站专门定制的解决方案,提供设计、实施和高效管理那些面向用户的搜索体验所需的工具。", - "xpack.enterpriseSearch.appSearch.productDescription": "利用仪表板、分析和 API 执行高级应用程序搜索简单易行。", - "xpack.enterpriseSearch.appSearch.productName": "App Search", - "xpack.enterpriseSearch.appSearch.result.clicks": "{clicks} 次点击", - "xpack.enterpriseSearch.appSearch.result.documentDetailLink": "访问文档详情", - "xpack.enterpriseSearch.appSearch.result.hideAdditionalFields": "隐藏其他字段", - "xpack.enterpriseSearch.appSearch.result.resultPositionLabel": "#{resultPosition}", - "xpack.enterpriseSearch.appSearch.result.showAdditionalFields": "显示其他 {numberOfAdditionalFields, number} 个{numberOfAdditionalFields, plural, other {字段}}", - "xpack.enterpriseSearch.appSearch.result.title": "文档 {id}", - "xpack.enterpriseSearch.appSearch.roleMappingCreatedMessage": "您的角色映射已创建", - "xpack.enterpriseSearch.appSearch.roleMappingDeletedMessage": "您的角色映射已删除", - "xpack.enterpriseSearch.appSearch.roleMappingsEngineAccessHeading": "引擎访问", - "xpack.enterpriseSearch.appSearch.roleMappingUpdatedMessage": "您的角色映射已更新", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.buttonLabel": "试用示例引擎", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.description": "使用示例数据测试引擎。", - "xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.title": "刚做过测试?", - "xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label": "记录分析事件", - "xpack.enterpriseSearch.appSearch.settings.logRetention.api.label": "记录 API 事件", - "xpack.enterpriseSearch.appSearch.settings.logRetention.audit.label": "记录审计事件", - "xpack.enterpriseSearch.appSearch.settings.logRetention.crawler.label": "网络爬虫日志", - "xpack.enterpriseSearch.appSearch.settings.logRetention.description": "日志保留由适用于您的部署的 ILM 策略决定。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore": "详细了解企业搜索的日志保留。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.description": "禁用写入时,引擎将停止记录分析事件。您的已有数据将根据存储期限进行删除。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.subheading": "当前您的分析日志将存储 {minAgeDays} 天。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.title": "禁用分析写入", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.description": "禁用写入时,引擎将停止记录 API 事件。您的已有数据将根据存储期限进行删除。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.subheading": "当前您的 API 日志将存储 {minAgeDays} 天。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.title": "禁用 API 写入", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.description": "禁用写入时,引擎将停止记录审计事件。您的已有数据将根据存储期限进行删除。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.subheading": "当前您的审计日志将存储 {minAgeDays} 天。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.title": "禁用审计写入", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.description": "禁用写入时,引擎将停止记录网络爬虫事件。您的已有数据将根据存储期限进行删除。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.subheading": "当前您的网络爬虫日志将存储 {minAgeDays} 天。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.title": "禁用网络爬虫写入", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.disable": "禁用", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.recovery": "您无法恢复删除的数据。", - "xpack.enterpriseSearch.appSearch.settings.logRetention.modal.save": "保存设置", - "xpack.enterpriseSearch.appSearch.settings.logRetention.title": "日志保留", - "xpack.enterpriseSearch.appSearch.settings.title": "设置", - "xpack.enterpriseSearch.appSearch.setupGuide.description": "获取工具来设计强大的搜索并将其部署到您的网站和移动应用程序。", - "xpack.enterpriseSearch.appSearch.setupGuide.notConfigured": "App Search 在您的 Kibana 实例中尚未得到配置。", - "xpack.enterpriseSearch.appSearch.setupGuide.videoAlt": "App Search 入门 - 在此视频中,我们将指导您如何开始使用 App Search", - "xpack.enterpriseSearch.appSearch.sourceEngines.removeEngineButton.label": "从元引擎中移除", - "xpack.enterpriseSearch.appSearch.specificEnginesDescription": "静态分配给选定的一组引擎。", - "xpack.enterpriseSearch.appSearch.specificEnginesLabel": "分配给特定引擎", - "xpack.enterpriseSearch.appSearch.tokens.admin.description": "私有管理员密钥用于与凭据 API 进行交互。", - "xpack.enterpriseSearch.appSearch.tokens.admin.name": "私有管理员密钥", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.all": "全部", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.readonly": "只读", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.readwrite": "读取/写入", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.search": "搜索", - "xpack.enterpriseSearch.appSearch.tokens.permissions.display.writeonly": "只写", - "xpack.enterpriseSearch.appSearch.tokens.private.description": "私有 API 密钥用于一个或多个引擎上的读和/写访问。", - "xpack.enterpriseSearch.appSearch.tokens.private.name": "私有 API 密钥", - "xpack.enterpriseSearch.appSearch.tokens.search.description": "公有搜索密钥仅用于搜索终端。", - "xpack.enterpriseSearch.appSearch.tokens.search.name": "公有搜索密钥", "xpack.enterpriseSearch.attachIndexBox.createSameIndexButtonLabel": "创建并附加名为 {indexName} 的索引", "xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexErrorTextLabel": "无法使用现有索引名称创建新索引。请选择现有索引,或使用新名称创建新索引。", "xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexHelpTextLabel": "您可以使用现有索引,也可以创建新索引。", @@ -17588,7 +16692,6 @@ "xpack.enterpriseSearch.crawler.startCrawlContextMenu.reapplyCrawlRulesMenuLabel": "重新应用爬网规则", "xpack.enterpriseSearch.crawler.urlComboBox.invalidUrlErrorMessage": "请输入有效 URL", "xpack.enterpriseSearch.crawlerEmptyState.h2.createYourFirstWebLabel": "创建您的首个网络爬虫", - "xpack.enterpriseSearch.crawlerEmptyState.newWebCrawlerButtonLabel": "新网络爬虫", "xpack.enterpriseSearch.crawlerEmptyState.openSourceCrawlerButtonLabel": "源代码", "xpack.enterpriseSearch.crawlerEmptyState.p.discoverExtractAndIndexLabel": "发现、提取和索引网站和知识库中的可搜索内容", "xpack.enterpriseSearch.crawlers.title": "Elasticsearch 网络爬虫", @@ -17648,8 +16751,6 @@ "xpack.enterpriseSearch.createConnector.startStep.p.whereDoYouWantLabel": "您希望将连接器存储在什么位置?您希望如何管理连接器?", "xpack.enterpriseSearch.createConnector.startStep.p.youWillStartTheLabel": "您将手动开始创建新索引、API 密钥和网络爬虫连接器 ID 的过程。(可选)您也可以提供自己的配置。", "xpack.enterpriseSearch.createConnector.startStep.startLabel": "启动", - "xpack.enterpriseSearch.curations.settings.licenseUpgradeLink": "详细了解许可证升级", - "xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel": "开始为期 30 天的试用", "xpack.enterpriseSearch.defaultSettingsFlyout.body.description.ingestPipelinesLink.link": "采集管道", "xpack.enterpriseSearch.defaultSettingsFlyout.body.description.label": "这些设置适用于由 Search 采集机制创建的所有新 Elasticsearch 索引。对于基于 API 采集的索引,在采集文档时请记得包括管道。这些功能由 {link} 提供支持", "xpack.enterpriseSearch.defaultSettingsFlyout.callout.body": "您还可以在索引的配置页面针对特定索引启用或禁用此功能。", @@ -17672,8 +16773,6 @@ "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "随时随地进行全面搜索。为工作繁忙的团队轻松实现强大的现代搜索体验。将预先调整的搜索功能快速添加到您的网站、应用或工作区。全面搜索就是这么简单。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "企业搜索尚未在您的 Kibana 实例中配置。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "企业搜索入门", - "xpack.enterpriseSearch.enterpriseSearchCard.cta": "了解详情", - "xpack.enterpriseSearch.entSearch.productCardDescription": "为构建更简单、用户友好且以业务为中心的搜索体验而量身定制的独立应用程序。", "xpack.enterpriseSearch.exampleConnectorLabel": "示例", "xpack.enterpriseSearch.finishUpStep.euiButton.viewInDiscoverLabel": "在 Discover 中查看", "xpack.enterpriseSearch.getConnectorTypeBadge.connectorClientBadgeLabel": "自管型", @@ -17753,8 +16852,6 @@ "xpack.enterpriseSearch.ingestSelector.method.connectorButtonLabel": "创建连接器", "xpack.enterpriseSearch.ingestSelector.method.connectors": "连接器", "xpack.enterpriseSearch.ingestSelector.method.connectors.description": "提取、转换、索引和同步来自第三方数据源的数据。", - "xpack.enterpriseSearch.ingestSelector.method.crawler": "网络爬虫", - "xpack.enterpriseSearch.ingestSelector.method.crawler.description": "发现、提取和索引网站和知识库中的可搜索内容。", "xpack.enterpriseSearch.ingestSelector.method.fileUpload": "上传文件", "xpack.enterpriseSearch.ingestSelector.method.fileUpload.description": "分隔的文本文件,例如 CSV 和 TSV、换行符分隔的 JSON。", "xpack.enterpriseSearch.ingestSelector.method.fileUploadLabel": "选择文件", @@ -17763,12 +16860,9 @@ "xpack.enterpriseSearch.ingestSelector.method.languageClients.description": "浏览我们支持的所有语言客户端以及如何与 API 结合使用。", "xpack.enterpriseSearch.ingestSelector.method.sampleData": "样例数据", "xpack.enterpriseSearch.ingestSelector.method.sampleData.description": "使用可供搜索的数据示例尝试搜索体验。", - "xpack.enterpriseSearch.ingestSelector.method.sourceCodeButtonLabel": "源代码", "xpack.enterpriseSearch.inlineEditableTable.newRowButtonLabel": "新行", "xpack.enterpriseSearch.integrations.apiDescription": "通过 Elasticsearch 稳健的 API 将搜索功能添加到您的应用程序。", "xpack.enterpriseSearch.integrations.apiName": "API", - "xpack.enterpriseSearch.integrations.webCrawlerDescription": "通过网络爬虫将搜索功能添加到您的网站。", - "xpack.enterpriseSearch.integrations.webCrawlerName": "网络爬虫", "xpack.enterpriseSearch.invalidJsonError": "JSON 无效", "xpack.enterpriseSearch.languages.cURL": "cURL", "xpack.enterpriseSearch.languages.cURL.githubLink": "curl", @@ -17811,7 +16905,6 @@ "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "正在计划", "xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "同步规则", "xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "搜索应用程序", - "xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "引擎", "xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "连接器", "xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "索引", "xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "网络爬虫", @@ -17858,84 +16951,9 @@ "xpack.enterpriseSearch.productSelector.overview.description": "构建搜索体验的第一步是创建搜索优化 Elasticsearch 索引并在其中导入内容。Elasticsearch 提供了一些用户友好的选项,您可以选择最能匹配您的技术专长和数据源的选项。", "xpack.enterpriseSearch.productSelector.overview.title": "采集您的内容", "xpack.enterpriseSearch.productSelectorCalloutTitle": "进行升级以便为您的团队获取企业级功能", - "xpack.enterpriseSearch.readOnlyMode.warning": "企业搜索处于只读模式。您将无法执行更改,例如创建、编辑或删除。", "xpack.enterpriseSearch.required": "必需", "xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel": "检查要求", "xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel": "请参阅此连接器的文档了解连接到 {serviceType} 的先决条件和配置要求。", - "xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "添加映射", - "xpack.enterpriseSearch.roleMapping.addUserLabel": "添加用户", - "xpack.enterpriseSearch.roleMapping.allLabel": "全部", - "xpack.enterpriseSearch.roleMapping.anyAuthProviderLabel": "任何当前或未来的身份验证提供程序", - "xpack.enterpriseSearch.roleMapping.attributeSelectorTitle": "属性映射", - "xpack.enterpriseSearch.roleMapping.attributeValueLabel": "属性值", - "xpack.enterpriseSearch.roleMapping.deactivatedLabel": "已停用", - "xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutDescription": "此用户当前未处于活动状态,访问权限已暂时吊销。可以通过 Kibana 控制台的'用户管理'区域重新激活用户。", - "xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutLabel": "用户已停用", - "xpack.enterpriseSearch.roleMapping.deleteRoleMappingDescription": "请注意,删除映射是永久性的,无法撤消", - "xpack.enterpriseSearch.roleMapping.emailLabel": "电子邮件", - "xpack.enterpriseSearch.roleMapping.enableRolesButton": "启用基于角色的访问", - "xpack.enterpriseSearch.roleMapping.enableRolesLink": "详细了解基于角色的访问", - "xpack.enterpriseSearch.roleMapping.enableUsersLink": "详细了解用户管理", - "xpack.enterpriseSearch.roleMapping.enginesLabel": "引擎", - "xpack.enterpriseSearch.roleMapping.existingInvitationLabel": "用户尚未接受邀请。", - "xpack.enterpriseSearch.roleMapping.existingUserLabel": "添加现有用户", - "xpack.enterpriseSearch.roleMapping.externalAttributeLabel": "外部属性", - "xpack.enterpriseSearch.roleMapping.externalAttributeTooltip": "外部属性由身份提供程序定义,会因服务不同而不同。", - "xpack.enterpriseSearch.roleMapping.filterRoleMappingsPlaceholder": "筛选角色映射", - "xpack.enterpriseSearch.roleMapping.filterUsersLabel": "筛选用户", - "xpack.enterpriseSearch.roleMapping.flyoutCreateTitle": "创建角色映射", - "xpack.enterpriseSearch.roleMapping.flyoutDescription": "基于用户属性分配角色和权限", - "xpack.enterpriseSearch.roleMapping.flyoutUpdateTitle": "更新角色映射", - "xpack.enterpriseSearch.roleMapping.groupsLabel": "组", - "xpack.enterpriseSearch.roleMapping.individualAuthProviderLabel": "选择单个身份验证提供程序", - "xpack.enterpriseSearch.roleMapping.invitationDescription": "此 URL 可共享给用户,允许他们接受企业搜索邀请和设置新密码", - "xpack.enterpriseSearch.roleMapping.invitationLink": "企业搜索邀请链接", - "xpack.enterpriseSearch.roleMapping.invitationPendingLabel": "邀请未决", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningDescription": "考虑为他们提供'enterprise-search-user'角色。", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningErrorMessage": "此 Elasticsearch 用户在 Elasticsearch 没有 Enterprise Search 角色。他们可能无法访问 Kibana。", - "xpack.enterpriseSearch.roleMapping.kibanaAccessWarningTitle": "Kibana 访问警告", - "xpack.enterpriseSearch.roleMapping.manageRoleMappingTitle": "管理角色映射", - "xpack.enterpriseSearch.roleMapping.newInvitationLabel": "邀请 URL", - "xpack.enterpriseSearch.roleMapping.newRoleMappingTitle": "添加角色映射", - "xpack.enterpriseSearch.roleMapping.newUserDescription": "提供粒度访问和权限", - "xpack.enterpriseSearch.roleMapping.newUserLabel": "创建新用户", - "xpack.enterpriseSearch.roleMapping.noResults.message": "未找到匹配的角色映射", - "xpack.enterpriseSearch.roleMapping.notFoundMessage": "未找到匹配的角色映射。", - "xpack.enterpriseSearch.roleMapping.noUsersDescription": "可灵活地分别添加用户。角色映射提供更宽广的接口,可以使用户属性添加更大数量的用户。", - "xpack.enterpriseSearch.roleMapping.noUsersLabel": "未找到匹配的用户", - "xpack.enterpriseSearch.roleMapping.noUsersTitle": "未添加任何用户", - "xpack.enterpriseSearch.roleMapping.rbacButtonDisabledLabel": "启用 RBAC 可以由超级用户执行。", - "xpack.enterpriseSearch.roleMapping.removeRoleMappingButton": "移除映射", - "xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle": "移除角色映射", - "xpack.enterpriseSearch.roleMapping.removeUserButton": "移除用户", - "xpack.enterpriseSearch.roleMapping.requiredLabel": "必需", - "xpack.enterpriseSearch.roleMapping.roleLabel": "角色", - "xpack.enterpriseSearch.roleMapping.roleMappingFlyoutCreateButton": "创建映射", - "xpack.enterpriseSearch.roleMapping.roleMappingFlyoutUpdateButton": "更新映射", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingButton": "创建新的角色映射", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDescription": "角色映射提供将原生或 SAML 控制的角色属性与 {productName} 权限关联的接口。", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDocsLink": "详细了解角色映射。", - "xpack.enterpriseSearch.roleMapping.roleMappingsHeadingTitle": "角色映射", - "xpack.enterpriseSearch.roleMapping.roleMappingsTitle": "用户和角色", - "xpack.enterpriseSearch.roleMapping.roleModalText": "移除角色映射可能会撤销当前已登录用户的访问权限。在继续之前,请通过不同角色映射验证当前已登录用户是否具有相应访问级别,以避免意外行为。此操作可能无法立即对 SAML 控制的角色生效。具有活动 SAML 会话的用户将保留访问权限,直到会话过期。", - "xpack.enterpriseSearch.roleMapping.rolesDisabledDescription": "为此部署设置的所有用户当前对 {productName} 具有完全访问权限。要限制访问和管理权限,必须为企业搜索启用基于角色的访问。", - "xpack.enterpriseSearch.roleMapping.rolesDisabledNote": "注意:启用基于角色的访问会限制对 App Search 和 Workplace Search 的访问。启用后,在适用的情况下,查看这两个产品的访问管理。", - "xpack.enterpriseSearch.roleMapping.rolesDisabledTitle": "基于角色的访问已禁用", - "xpack.enterpriseSearch.roleMapping.saveRoleMappingButtonLabel": "保存角色映射", - "xpack.enterpriseSearch.roleMapping.smtpCalloutLabel": "在以下情况下,个性化邀请将自动发送:当提供企业搜索", - "xpack.enterpriseSearch.roleMapping.smtpLinkLabel": "SMTP 配置时", - "xpack.enterpriseSearch.roleMapping.updateRoleMappingButtonLabel": "更新角色映射", - "xpack.enterpriseSearch.roleMapping.updateUserDescription": "管理粒度访问和权限", - "xpack.enterpriseSearch.roleMapping.updateUserLabel": "更新用户", - "xpack.enterpriseSearch.roleMapping.userAddedLabel": "用户已添加", - "xpack.enterpriseSearch.roleMapping.userModalText": "移除用户会立即吊销对该体验的访问,除非此用户的属性也对应于原生和 SAML 控制身份验证的角色映射,在这种情况下,还应该根据需要复查并调整关联的角色映射。", - "xpack.enterpriseSearch.roleMapping.userModalTitle": "移除 {username}", - "xpack.enterpriseSearch.roleMapping.usernameLabel": "用户名", - "xpack.enterpriseSearch.roleMapping.usernameNoUsersText": "没有符合添加资格的现有用户。", - "xpack.enterpriseSearch.roleMapping.usersHeadingDescription": "用户管理针对个别或特殊的权限需要提供粒度访问。可能会从此列表排除一些用户。包括来自联合源(如 SAML)的用户,按照角色映射及内置用户帐户进行管理,如'elastic'或'enterprise_search'用户。\n", - "xpack.enterpriseSearch.roleMapping.usersHeadingLabel": "添加新用户", - "xpack.enterpriseSearch.roleMapping.usersHeadingTitle": "用户", - "xpack.enterpriseSearch.roleMapping.userUpdatedLabel": "用户已更新", "xpack.enterpriseSearch.save": "保存", "xpack.enterpriseSearch.schema.addFieldModal.addFieldButtonLabel": "添加字段", "xpack.enterpriseSearch.schema.addFieldModal.description": "字段添加后,将无法从架构中删除。", @@ -18147,9 +17165,7 @@ "xpack.enterpriseSearch.searchExperiences.guide.featuresTitle": "功能", "xpack.enterpriseSearch.searchExperiences.guide.githubLink": "GitHub 上的搜索 UI", "xpack.enterpriseSearch.searchExperiences.guide.pageTitle": "使用搜索 UI 构建搜索体验", - "xpack.enterpriseSearch.searchExperiences.guide.tutorials.appSearch.description": "使用 App Search 和搜索 UI 构建搜索体验。", "xpack.enterpriseSearch.searchExperiences.guide.tutorials.elasticsearch.description": "使用 Elasticsearch 和搜索 UI 构建搜索体验。", - "xpack.enterpriseSearch.searchExperiences.guide.tutorials.workplaceSearch.description": "使用 Workplace Search 和搜索 UI 构建搜索体验。", "xpack.enterpriseSearch.searchExperiences.guide.tutorialsTitle": "即刻开始使用教程", "xpack.enterpriseSearch.searchExperiences.navTitle": "搜索体验", "xpack.enterpriseSearch.searchExperiences.productDescription": "构建直观、具有吸引力的搜索体验,而无需浪费时间进行重复工作。", @@ -18167,7 +17183,6 @@ "xpack.enterpriseSearch.searchNav.otherTools": "其他工具", "xpack.enterpriseSearch.searchNav.relevance": "相关性", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "搜索 AI", - "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Elastic 网络爬虫", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "单击以打开连接器说明弹出框", "xpack.enterpriseSearch.selectConnector.connectorClientBadgeLabel": "自管型", "xpack.enterpriseSearch.selectConnector.h4.connectorClientsLabel": "自管型连接器", @@ -18197,11 +17212,7 @@ "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionExistsError": "集合名称已存在。请选择其他名称。", "xpack.enterpriseSearch.server.routes.addAnalyticsCollection.analyticsCollectionNotFoundErrorMessage": "未找到分析集合", "xpack.enterpriseSearch.server.routes.addConnector.connectorExistsError": "连接器或索引已存在", - "xpack.enterpriseSearch.server.routes.addCrawler.connectorExistsError": "此索引的连接器已存在", - "xpack.enterpriseSearch.server.routes.addCrawler.crawlerExistsError": "此索引的网络爬虫已存在", - "xpack.enterpriseSearch.server.routes.addCrawler.indexExistsError": "此索引已存在", "xpack.enterpriseSearch.server.routes.checkKibanaLogsMessage": "{errorMessage} 请查阅 Kibana 服务器日志了解详情。", - "xpack.enterpriseSearch.server.routes.configData.errorMessage": "从 Enterprise Search 中提取数据时出错", "xpack.enterpriseSearch.server.routes.connectors.expensive_query_not_allowed_error": "不允许执行资源密集型搜索查询。已将'search.allow_expensive_queries'设置为 false", "xpack.enterpriseSearch.server.routes.connectors.generateConfiguration.indexAlreadyExistsError": "找不到唯一的连接器名称", "xpack.enterpriseSearch.server.routes.connectors.resource_not_found_error": "找不到 ID 为 {connectorId} 的连接器。", @@ -18212,17 +17223,14 @@ "xpack.enterpriseSearch.server.routes.createSearchApplication.searchApplciationExistsError": "搜索应用程序名称已占用。请选择其他名称。", "xpack.enterpriseSearch.server.routes.createSearchApplication.searchApplicationInvalidName": "搜索应用程序名称无效。{exceptionReason}", "xpack.enterpriseSearch.server.routes.errorLogMessage": "解析 {requestUrl} 请求时出错:{errorMessage}", - "xpack.enterpriseSearch.server.routes.fetchCrawlerMultipleSchedules.documentNotFoundError": "找不到网络爬虫数据。", "xpack.enterpriseSearch.server.routes.fetchSearchApplicationFieldCapabilities.error": "找不到搜索应用程序", "xpack.enterpriseSearch.server.routes.fetchSearchApplicationFieldCapabilities.missingAliasError": "搜索应用程序别名缺失。", "xpack.enterpriseSearch.server.routes.indices.existsErrorLogMessage": "解析 {requestUrl} 请求时出错", "xpack.enterpriseSearch.server.routes.indices.pipelines.indexMissingError": "索引 {indexName} 不存在", "xpack.enterpriseSearch.server.routes.indices.pipelines.pipelineMissingError": "管道 {pipelineName} 不存在", "xpack.enterpriseSearch.server.routes.indices.pipelines.pipelineNotFoundError": "管道 {pipelineName} 不存在", - "xpack.enterpriseSearch.server.routes.recreateConnector.connectorExistsError": "此索引的连接器已存在", "xpack.enterpriseSearch.server.routes.unauthorizedError": "您的权限不足。", "xpack.enterpriseSearch.server.routes.uncaughtExceptionError": "搜索遇到错误。", - "xpack.enterpriseSearch.server.routes.updateHtmlExtraction.noCrawlerFound": "找不到此索引的网络爬虫", "xpack.enterpriseSearch.server.utils.invalidEnumValue": "字段 {fieldName} 的非法值 {value}", "xpack.enterpriseSearch.setupGuide.cloud.step1.instruction1": "访问 Elastic Cloud 控制台以{editDeploymentLink}。", "xpack.enterpriseSearch.setupGuide.cloud.step1.instruction1LinkText": "编辑您的部署", @@ -18299,754 +17307,7 @@ "xpack.enterpriseSearch.whatsNextBox.searchPlaygroundButtonLabel": "搜索 Playground", "xpack.enterpriseSearch.whatsNextBox.whatsNextPanelDescription": "您可以手动同步数据,计划重复同步或参阅文档。", "xpack.enterpriseSearch.whatsNextBox.whatsNextPanelLabel": "后续操作", - "xpack.enterpriseSearch.workplaceSearch.accountNav.account.link": "我的帐户", - "xpack.enterpriseSearch.workplaceSearch.accountNav.logout.link": "注销", - "xpack.enterpriseSearch.workplaceSearch.accountNav.orgDashboard.link": "前往组织仪表板", - "xpack.enterpriseSearch.workplaceSearch.accountNav.search.link": "搜索", - "xpack.enterpriseSearch.workplaceSearch.accountNav.settings.link": "帐户设置", - "xpack.enterpriseSearch.workplaceSearch.accountNav.sources.link": "内容源", - "xpack.enterpriseSearch.workplaceSearch.accountSettings.description": "管理访问权限、密码和其他帐户设置。", - "xpack.enterpriseSearch.workplaceSearch.accountSettings.title": "帐户设置", - "xpack.enterpriseSearch.workplaceSearch.activityFeedEmptyDefault.title": "您的组织最近无活动", - "xpack.enterpriseSearch.workplaceSearch.activityFeedNamedDefault.title": "{name} 最近无活动", - "xpack.enterpriseSearch.workplaceSearch.add.label": "添加", - "xpack.enterpriseSearch.workplaceSearch.addField.label": "添加字段", - "xpack.enterpriseSearch.workplaceSearch.addSource.addSourceHeader.externalConnectorLabel": "外部部署的连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.and": "且", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteLabel": "是否确定要删除此 API 密钥?此操作无法撤消。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.confirmDeleteTitle": "删除 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copied.tooltip": "已复制", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiEndpoint.buttonLabel": "将 API 终结点复制到剪贴板。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.copyApiKey.buttonLabel": "将 API 密钥复制到剪贴板。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.createKey.buttonLabel": "创建密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.deleteApiKey.buttonDescription": "删除 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyBody": "允许应用程序代表您访问 Elastic Workplace Search。", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyButtonLabel": "了解 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.emptyTitle": "创建您的首个 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.endpointTitle": "终端", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.flyoutTitle": "创建新密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.formHelpText": "您的密钥将命名为:{name}", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.formLabel": "密钥名称", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.hideApiKeyLabel": "隐藏 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.keyTitle": "钥匙", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.namePlaceholder": "例如,my-api-key", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.nameTitle": "名称", - "xpack.enterpriseSearch.workplaceSearch.apiKeys.showApiKeyLabel": "显示 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.baseUri.label": "基 URI", - "xpack.enterpriseSearch.workplaceSearch.baseUrl.label": "基 URL", - "xpack.enterpriseSearch.workplaceSearch.betweenLabel": "介于", - "xpack.enterpriseSearch.workplaceSearch.blockLabel": "阻止", - "xpack.enterpriseSearch.workplaceSearch.clientId.label": "客户端 ID", - "xpack.enterpriseSearch.workplaceSearch.clientSecret.label": "客户端密钥", - "xpack.enterpriseSearch.workplaceSearch.comfirmModal.title": "请确认", - "xpack.enterpriseSearch.workplaceSearch.confidential.label": "保密", - "xpack.enterpriseSearch.workplaceSearch.confidential.text": "为客户端密钥无法保密的环境(如原生移动应用和单页面应用程序)取消选择。", - "xpack.enterpriseSearch.workplaceSearch.configure.button": "配置", - "xpack.enterpriseSearch.workplaceSearch.confirmChanges.text": "确认更改", - "xpack.enterpriseSearch.workplaceSearch.connectors.header.description": "您的所有可配置连接器。", - "xpack.enterpriseSearch.workplaceSearch.connectors.header.title": "内容源连接器", - "xpack.enterpriseSearch.workplaceSearch.consumerKey.label": "使用者密钥", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.description": "要准备进行配置,请查阅我们的{deploymentGuideLink}了解快速部署连接器软件包所需满足的所有先决条件。通过在下一步中设置连接器的 URL 和 API 密钥,在 Enterprise Search 中确定您的配置。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.heading": "{name} 完全可定制,并将在您选择的基础架构上进行自我管理。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addExternalConnector.documentation.linkLabel": "文档", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.description": "为您的用例构建、修改和部署连接器软件包。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.learnMoreButtonLabel": "了解详情", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.registerButtonLabel": "注册您的部署", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.reviewButtonLabel": "复查连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.byoSourcePrompt.title": "没有看到要查找的内容?", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.configCompleted.feedbackCallOutText": "具有关于部署 {name} 连接器软件包的反馈?请告诉我们。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.apiKeyLabel": "连接器 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.error": "请使用有效的 URL", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.helpText": "URL 应以 https:// 开头", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.insecureTitle": "连接不安全", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.insecureWarning": "您的连接器使用的是 HTTP 连接,这不是专用连接。其他人可以查看此连接器同步的信息。请通过 HTTPS 建立连接以确保您的信息安全。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.registerButtonLabel": "注册部署", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.stepTitle": "提供适当的配置信息", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSource.externalConnectorConfig.urlLabel": "连接器 URL", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.emptyBody": "管理员将源添加到此组织后,它们便可供搜索。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.emptyTitle": "没有可用源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.newSourceDescription": "配置并连接源时,您将会使用从内容平台本身同步的可搜索内容创建不同的实体。可以使用以下一个可用连接器添加源,也可以通过定制 API 源,实现更多的灵活性。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.noSourcesTitle": "配置并连接您的首个内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourceDescription": "组织内容源可供整个组织使用,也可以分配给指定用户组。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourcesTitle": "添加组织内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.placeholder": "筛选源......", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.privateSourceDescription": "连接新源以将其内容和文档添加到您搜索体验中。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.privateSourcesTitle": "添加新的内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.body": "配置可用源,或构建自己的,方法是使用", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.connectButtonLabel": "连接", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.customSource.button": "定制 API 源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.emptyState": "没有可用源匹配您的查询。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.title": "可配置", - "xpack.enterpriseSearch.workplaceSearch.contentSource.availableSourceList.toolTipContent": "{name} 可配置为专用源,适用于白金级订阅。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.back.button": "返回", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.alt.text": "连接图示", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.configure.button": "注册您的部署", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.heading": "第 1 步", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.repositoryLinkLabel": "存储库", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.text": "在连接器软件包 {repositoryLink} 中,提供了您了解连接器框架并设置编码环境所需全部内容。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step1.title": "构建或修改代码", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.documentationLinkLabel": "文档", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.heading": "第 2 步", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.text": "连接器软件包将在您部署的基础架构上进行自我管理。查看 {documentationLink} 了解开始进行部署的先决条件。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step2.title": "部署定制连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.step3.text": "构建并部署连接器软件包后,请返回此处注册连接器软件包部署,完成配置并连接到内容源。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.byoConfigIntro.steps.title": "构建并部署定制连接器软件包,以添加来自定制内容源的数据,或修改第一方内容源的行为", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.configureNew.button": "配置新的内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.connect.button": "连接 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.heading": "{name} 已配置", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.orgCanConnect.message": "{name} 现在可连接到 Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.personalConnectLink.message": "用户现在可从个人仪表板上链接自己的 {name} 帐户。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.button": "详细了解专用内容源。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.link": "启用专用源连接", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.message": "切记在安全设置中{securityLink}。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.button": "创建定制 API 源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.createNamedSourceButtonLabel": "配置 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.description": "要准备进行配置,请查阅我们的{deploymentGuideLink}了解快速部署连接器软件包所需满足的所有先决条件。在 Enterprise Search 中使用 {name} 内容源的描述性名称确定您的配置,然后用在下一步中提供的源 ID 更新连接器配置文件。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.discussLinkLabel": "有疑问?请在此处讨论。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.feedbackLinkLabel": "我们始终在努力做出改进。分享您的反馈", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.githubRepoLinkLabel": "在此处定制连接器。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.heading": "{name} 连接器完全可定制,并将在您选择的基础架构上进行自我管理。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.deploymentGuide.linkLabel": "文档", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.description": "{link}以详细了解定制 API 源。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.text": "阅读文档", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.title": "如何添加 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.applicationPortal.button": "{name} 应用程序门户", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.discuss.buttonLabel": "有疑问?请在此处讨论。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.button": "说明", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.description": "设置定制连接器以提高可配置性和控制能力。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.custom.title": "定制连接器", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.betaLabel": "技术预览", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.button": "说明", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.description": "在自我管理基础架构上部署此连接器软件包以用于高级用例。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.recommendedLabel": "推荐", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.reviewButtonLabel": "复查连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.external.title": "连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.button": "连接", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.description": "使用此连接器快速开始,而无需部署其他基础架构。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configExternalChoice.internal.title": "连接器", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.alt.text": "连接图示", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button": "配置 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.badge": "一次性操作", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.heading": "第 1 步", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.text": "通过您或您的团队用于连接并同步内容的内容源设置安全的 OAuth 应用程序。只需对每个内容源执行一次。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.title": "配置 OAuth 应用程序 {badge}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.heading": "第 2 步", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.text": "使用新的 OAuth 应用程序将内容源任何数量的实例连接到 Workplace Search。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step2.title": "连接内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.text": "快速设置,让您的所有文档都可搜索。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.title": "如何添加 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.button": "完成连接", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.label": "选择要同步的 GitHub 组织", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.accountOnlyTooltip": "专用内容源。每个用户必须从自己的个人仪表板添加内容源。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.body": "已配置且准备好连接。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.connectAnotherButton": "连接另一个", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.connectButton": "连接", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.emptyState": "没有已配置的源匹配您的查询。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.title": "已配置内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSources.unConnectedTooltip": "没有已连接源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configuredSourcesList.betaBadge": "公测版", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.button": "连接 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.docPermissionsUnavailable.message": "尚没有文档级别权限可用于此源。{link}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.needsPermissions.text": "将同步文档级别权限信息。在初始连接后,需要进行其他配置,然后文档才可供搜索。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.notSynced.text": "连接服务可访问的所有文档将同步,并提供给组织的用户或组的用户。文档立即可供搜索。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.notSynced.title": "将不同步文档级别权限", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.permissions.label": "启用文档级别权限同步", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.permissions.title": "文档级权限", - "xpack.enterpriseSearch.workplaceSearch.contentSource.connect.whichOption.link": "我应选哪个选项?", - "xpack.enterpriseSearch.workplaceSearch.contentSource.formSourceAddedSuccessMessage": "{name} 已连接", - "xpack.enterpriseSearch.workplaceSearch.contentSource.includedFeaturesTitle": "包括的功能", - "xpack.enterpriseSearch.workplaceSearch.contentSource.reAuthenticate.body": "您的 {name} 凭据不再有效。请使用原始凭据重新验证,以恢复内容同步。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.reAuthenticate.button": "重新验证 {name}", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.baseUrlLabel": "基 URL", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.button": "保存配置", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.clientIDLabel": "客户端 ID", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.clientSecretLabel": "客户端密钥", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.oauthStep1": "在组织的 {sourceName} 帐户中创建 OAuth 应用", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveConfig.oauthStep2": "提供适当的配置信息", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.body1": "您的终端已准备好接受请求。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.configureNewSourceButtonLabel": "配置新的内容源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.heading": "{name} 已创建", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.addField.button": "添加字段", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.description": "您索引一些文档后,系统便会为您创建架构。单击下面,以提前创建架构字段。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.empty.title": "内容源没有架构", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.dataType": "数据类型", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.header.fieldName": "字段名称", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.heading": "架构更改错误", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.errors.message": "糟糕,我们无法为此架构找到任何错误。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.fieldAdded.message": "新字段已添加。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.filter.placeholder": "筛选架构字段......", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.description": "添加新字段或更改现有字段的类型", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.manage.title": "管理源架构", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.newFieldExists.message": "新字段已存在:{fieldName}。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.save.button": "保存架构", - "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updated.message": "架构已更新。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.text": "文档级别权限根据定义的规则管理用户内容访问权限。允许或拒绝个人和组对特定文档的访问。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.title": "适用于白金级许可证的文档级别权限", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.description": "连接服务可访问的所有文档将同步,并提供给组织的用户或组的用户。文档立即可供搜索", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.title": "全局访问权限", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.description": "返回的结果特定于您且与您相关。连接此源不会将您的个人数据暴露给其他搜索用户 - 只有您可以看到。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.title": "始终专用", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.description": "在 Workplace Search 中,可实时搜索消息数据和其他信息。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.title": "始终保持最新", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.description": "以下项目可搜索:", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.title": "可搜索内容", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncedItems.title": "已同步的项目", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.text": "此源每 {duration} 从 {name} 获取新内容(在初始同步后)。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.time": "2 小时", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.title": "每 2 小时同步一次", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.color.label": "颜色", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.createdBy.label": "创建者", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.description": "对定制 API 源搜索结果的内容和样式进行定制。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.title": "显示设置", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.body": "您需要一些要显示的内容,以便配置显示设置。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.title": "您尚没有任何内容", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.emptyFields.description": "添加字段,并将进行相应移动以使它们按照所需顺序显示。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.description": "匹配文档将显示为单个卡片。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.title": "精选结果", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.go.button": "执行", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.lastUpdated.heading": "上次更新时间", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.mediaType.label": "媒体类型", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.optionalArea.text": "此区域可选", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.preview.title": "预览", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.reset.button": "重置", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.resultDetail.label": "结果详情", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.label": "搜索结果", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.title": "搜索结果设置", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResultsRow.helpText": "此区域可选", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.description": "部分匹配的文档将显示为集。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.title": "标准结果", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.subtitle.label": "子标题", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.success.message": "显示设置已成功更新。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.heading": "标题", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.label": "标题", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.type.label": "类型", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.unsaved.message": "您的显示设置尚未保存。是否确定要离开?", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.updatedBy.label": "更新者", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.url.label": "URL", - "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.visibleFields.title": "可见的字段", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.estimateSummaryLabel": "估计需要 {estimateDisplay}完成。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.frequencyItemLabel": "执行 {label} 的间隔:", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.lastRunLabel": "上次运行", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.lastRunSummary": "此同步 {lastRunStrong} {lastRunTime}。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartLabel": "下次运行", - "xpack.enterpriseSearch.workplaceSearch.contentSources.synchronization.nextStartSummary": "{nextStartStrong} 将开始 {nextStartTime}。", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementContentExtractionLabel": "从文件同步全文", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementGlobalConfigLabel": "同步缩略图 - 已在全局配置级别禁用", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementSynchronizeLabel": "同步此源", - "xpack.enterpriseSearch.workplaceSearch.contentSources.syncManagementThumbnailsLabel": "同步缩略图", - "xpack.enterpriseSearch.workplaceSearch.copied.tooltip": "已复制!", - "xpack.enterpriseSearch.workplaceSearch.copy.tooltip": "复制到剪贴板", - "xpack.enterpriseSearch.workplaceSearch.copyText": "复制", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.copied.tooltip": "已复制!", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.copy.tooltip": "复制到剪贴板", - "xpack.enterpriseSearch.workplaceSearch.credentialItem.show.tooltip": "显示 {credential}。", - "xpack.enterpriseSearch.workplaceSearch.credentials.description": "在您的客户端中使用以下凭据从我们的身份验证服务器请求访问令牌。", - "xpack.enterpriseSearch.workplaceSearch.credentials.title": "凭据", - "xpack.enterpriseSearch.workplaceSearch.cta": "浏览", - "xpack.enterpriseSearch.workplaceSearch.customize.header.description": "个性化常规组织设置。", - "xpack.enterpriseSearch.workplaceSearch.customize.header.title": "定制 Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.customize.name.button": "保存组织名称", - "xpack.enterpriseSearch.workplaceSearch.customize.name.label": "组织名称", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.documentationLinkLabel": "{name} 连接器文档", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.genericDocumentationHelpText": "查阅 {documentationLink}了解如何在您选择的自我管理基础架构上构建和部署您自己的连接器。", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.genericDocumentationLabel": "定制 API 源文档", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.preconfiguredDocumentationHelpText": "查阅{documentationLink}并在您选择的基础架构上部署将进行自我管理的连接器软件包。", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.preconfiguredRepositoryInstructions": "通过克隆 {githubRepositoryLink}设置您的连接器", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.repositoryLinkLabel": "{name} 连接器存储库", - "xpack.enterpriseSearch.workplaceSearch.customSourceDeployment.sourceIdentifierHelpText": "在部署的连接器的配置文件中指定以下源标识符以及 {apiKeyLink},以便同步文档。", - "xpack.enterpriseSearch.workplaceSearch.deployment.title": "部署", - "xpack.enterpriseSearch.workplaceSearch.description.label": "描述", - "xpack.enterpriseSearch.workplaceSearch.documentsHeader": "文档", - "xpack.enterpriseSearch.workplaceSearch.editField.label": "编辑字段", "xpack.enterpriseSearch.workplaceSearch.explorePlatinumFeatures.link": "了解白金级功能", - "xpack.enterpriseSearch.workplaceSearch.externalConnectorApiKey.label": "连接器 API 密钥", - "xpack.enterpriseSearch.workplaceSearch.externalConnectorUrl.label": "连接器 URL", - "xpack.enterpriseSearch.workplaceSearch.field.label": "字段", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.contact": "与您联系", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.description": "提交反馈即表示您确认您已阅读并同意我们的{termsOfService},并且 Elastic 可以使用您提供的上述详细信息{contact},以向您介绍我们相关的产品和服务。请参阅{privacyStatementLink}了解更多详情,或随时退出。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.Label": "是否要分享任何其他反馈?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.optionalLabel": "可选", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readDataPrivacyStatementLink": "Elastic 隐私声明", - "xpack.enterpriseSearch.workplaceSearch.gateForm.additionalFeedback.readTermsOfService": "服务条款", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.action.Label": "添加搜索分析", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureDescription": "您是否知道,您可以通过行为分析轻松分析用户的搜索和单击行为?检测您的网站或应用程序,以跟踪相关用户操作。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.analytics.featureName": "使用行为分析", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureButtonLabel": "使用连接器", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureDescription": "您是否知道,您可以通过 Elastic 连接器使用强大的内容提取功能!使用我们强大且高度适应的提取功能从文件中提取内容。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorExtraction.featureName": "使用 Elastic 连接器", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureButtonLabel": "使用连接器", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureDescription": "您是否知道,Elastic 连接器现在可供使用了?您可以使数据源上的内容与搜索优化的索引保持同步!", - "xpack.enterpriseSearch.workplaceSearch.gateForm.connectorSources.featureName": "使用 Elastic 连接器", - "xpack.enterpriseSearch.workplaceSearch.gateForm.description": "独立型 Workplace Search 产品在维护模式下仍然可用,但不建议使用它来获得新的搜索体验。相反,我们建议使用 Elasticsearch 原生工具集,我们的团队正针对 Workplace Search 用例积极开发和改进该工具集。这些工具为直接使用 Elasticsearch 索引提供了灵活性和可组合性。请在此{blogUrl}中了解有关本次重新聚焦的更多背景信息。为帮助选择最适合您用例的工具,我们创建了这个推荐向导。请告知您需要的功能,我们将引导您找到最佳解决方案。如果仍然希望在此时开始使用独立型 Workplace Search 产品,您可以在提交表单后开始。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.docLevelPermissions.featureDescription": "您是否知道,您可以根据用户和组权限,限制对 Elasticsearch 索引中的文档的访问权限?对于使用 Elastic 文档级安全性的用户,仅返回已授权搜索结果。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.docLevelPermissions.featureName": "使用 Elastic 连接器", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.learnMore": "了解详情", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.subTitle": "根据您的选择,我们建议:", - "xpack.enterpriseSearch.workplaceSearch.gateForm.educationalPanel.title": "Elasticsearch 本机等价项", - "xpack.enterpriseSearch.workplaceSearch.gateForm.featureOther.Label": "是否可以说明您在寻找其他哪些功能?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.features.Label": "您寻求使用哪些 Workplace Search 功能?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.features.selectOption": "选择选项", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label": "加入我们的用户研究调查以改进 Elasticsearch?", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label.No": "否", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.Label.Yes": "是", - "xpack.enterpriseSearch.workplaceSearch.gateForm.participateUxLab.optionalLabel": "可选", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.action.Label": "使用搜索 UI 构建搜索体验", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.addOn.learnMoreLabel": "搜索 UI", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureDescription": "充分利用 Elasticsearch 的强大功能的搜索驱动型应用程序!使用搜索应用程序构建统一搜索,或直接集成现有 UI 与搜索 UI。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.searchApplication.featureName": "创建搜索应用程序", - "xpack.enterpriseSearch.workplaceSearch.gateForm.submit": "提交", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.description": "记录并查看用户与搜索结果的交互", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.inputDisplay": "分析", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.analytics.title": "分析", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.description": "提取已同步源文件的内容,使其可进行搜索", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.inputDisplay": "内容提取", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentExtraction.title": "内容提取", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.description": "提取已同步源文件的内容,使其可进行搜索", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.inputDisplay": "内容源", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.contentSource.title": "内容源", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.description": "控制对特定文档的访问", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.inputDisplay": "文档级权限", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.docLevelPermissions.title": "文档级权限", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.description": "未在此处列出的其他功能", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.inputDisplay": "其他", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.other.title": "其他", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.description": "轻松构建搜索驱动型应用程序", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.inputDisplay": "开箱即用型搜索体验", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.searchApplication.title": "开箱即用型搜索体验", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.description": "链接意思相近的不同词或短语", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.inputDisplay": "同义词", - "xpack.enterpriseSearch.workplaceSearch.gateForm.superSelect.synonymns.title": "同义词", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.action.Label": "使用同义词进行搜索", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.featureDescription": "您是否知道,您可以通过使用同义词进行搜索来改进搜索体验?使用我们的同义词 API 轻松创建和管理同义词集。", - "xpack.enterpriseSearch.workplaceSearch.gateForm.synonyms.featureName": "使用同义词 API 进行搜索", - "xpack.enterpriseSearch.workplaceSearch.gateForm.title": "开始前......", - "xpack.enterpriseSearch.workplaceSearch.gateForm.viewBlog": "博客", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.heading": "添加组", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.label": "组名", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.submit.action": "添加组", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroupForm.action": "创建组", - "xpack.enterpriseSearch.workplaceSearch.groups.clearFilters.action": "清除筛选", - "xpack.enterpriseSearch.workplaceSearch.groups.contentSourceCountHeading": "{numSources} 个组织内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.description": "将组织内容源和用户分配到组,以便为各种内部团队打造相关搜索体验。", - "xpack.enterpriseSearch.workplaceSearch.groups.filterGroups.placeholder": "按名称筛选组......", - "xpack.enterpriseSearch.workplaceSearch.groups.filterSources.buttonText": "源", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "管理 {label}", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "可能您尚未添加任何组织内容源。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.title": "哎哟!", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.deselectButton.text": "取消全选", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.selectButton.text": "全选", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton": "添加组织源", - "xpack.enterpriseSearch.workplaceSearch.groups.groupPrioritizationUpdated": "已成功更新组织源的优先级排序。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "已成功更新组织内容源。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupPagination.label": "组", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "组", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "上次更新于 {updatedAt}。", - "xpack.enterpriseSearch.workplaceSearch.groups.heading": "管理组", - "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "邀请用户", - "xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action": "管理组", - "xpack.enterpriseSearch.workplaceSearch.groups.newGroupSavedSuccess": "已成功创建 {groupName}", - "xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage": "无组织内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveButtonText": "删除 {name}", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "您的组将从 Workplace Search 中删除。确定要移除 {name}?", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "确认", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "未与此组共享任何内容源。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "组内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "分配给此组的用户有权访问上面定义的源的数据和内容。可在'用户和角色'区域中管理此组的用户分配。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.manageSourcesButtonText": "管理组织内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.manageUsersButtonText": "管理用户和角色", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.nameSectionDescription": "定制此组的名称。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.nameSectionTitle": "组名", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeButtonText": "移除组", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeSectionDescription": "此操作无法撤消。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.removeSectionTitle": "移除此组", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.saveNameButtonText": "保存名称", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.usersSectionTitle": "组用户", - "xpack.enterpriseSearch.workplaceSearch.groups.searchResults.notFoound": "找不到结果。", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerDescription": "校准组内容源的相对文档重要性。", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerTitle": "组织内容源优先级排序", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.priorityTableHeader": "相关性优先级", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.sourceTableHeader": "源", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateBody": "与 {groupName} 共享两个或多个源,以定制源优先级排序。", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateButtonText": "添加组织内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateTitle": "未与此组共享任何源", - "xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalLabel": "组织内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalTitle": "选择要与 {groupName} 共享的内容源", - "xpack.enterpriseSearch.workplaceSearch.keepEditing.button": "继续编辑", - "xpack.enterpriseSearch.workplaceSearch.label.label": "标签", - "xpack.enterpriseSearch.workplaceSearch.name.label": "名称", - "xpack.enterpriseSearch.workplaceSearch.nav.addSource": "添加源", - "xpack.enterpriseSearch.workplaceSearch.nav.apiKeys": "API 密钥", - "xpack.enterpriseSearch.workplaceSearch.nav.content": "内容", - "xpack.enterpriseSearch.workplaceSearch.nav.displaySettings": "显示设置", - "xpack.enterpriseSearch.workplaceSearch.nav.groups": "组", - "xpack.enterpriseSearch.workplaceSearch.nav.groups.groupOverview": "概览", - "xpack.enterpriseSearch.workplaceSearch.nav.groups.sourcePrioritization": "源的优先级排序", - "xpack.enterpriseSearch.workplaceSearch.nav.overview": "概览", - "xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard": "查看我的个人仪表板", - "xpack.enterpriseSearch.workplaceSearch.nav.roleMappings": "用户和角色", - "xpack.enterpriseSearch.workplaceSearch.nav.schema": "架构", - "xpack.enterpriseSearch.workplaceSearch.nav.searchApplication": "前往搜索应用程序", - "xpack.enterpriseSearch.workplaceSearch.nav.security": "安全", - "xpack.enterpriseSearch.workplaceSearch.nav.settings": "设置", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsCustomize": "定制", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsOauth": "OAuth 应用程序", - "xpack.enterpriseSearch.workplaceSearch.nav.settingsSourcePrioritization": "内容源连接器", - "xpack.enterpriseSearch.workplaceSearch.nav.sources": "源", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronization": "同步", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationAssetsAndObjects": "资产和对象", - "xpack.enterpriseSearch.workplaceSearch.nav.synchronizationFrequency": "频率", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "配置 OAuth 应用程序,以安全使用 Workplace Search 搜索 API。升级到白金级许可证,以启用搜索 API 并创建您的 OAuth 应用程序。", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "正在为定制搜索应用程序配置 OAuth", - "xpack.enterpriseSearch.workplaceSearch.oauth.description": "为您的组织创建 OAuth 客户端。", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationDescription": "授权 {strongClientName} 使用您的帐户?", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationTitle": "需要授权", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizeButtonLabel": "授权", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.denyButtonLabel": "拒绝", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.httpRedirectWarningMessage": "此应用程序正使用不安全的重定向 URI (http)", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.scopesLeadInMessage": "此应用程序将能够", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.searchScopeDescription": "搜索您的数据", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.unknownScopeDescription": "{unknownAction}您的数据", - "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.writeScopeDescription": "修改您的数据", - "xpack.enterpriseSearch.workplaceSearch.oauthPersisted.description": "访问您的组织的 OAuth 客户端并管理 OAuth 设置。", - "xpack.enterpriseSearch.workplaceSearch.ok.button": "确定", - "xpack.enterpriseSearch.workplaceSearch.onLabel": "在", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "活动用户", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "邀请", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.organizationalSources": "组织源", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "专用源", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "使用统计", - "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "命名您的组织", - "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "在邀请同事之前,请命名您的组织以提升辨识度。", - "xpack.enterpriseSearch.workplaceSearch.overviewHeader.description": "您的组织的统计信息和活动", - "xpack.enterpriseSearch.workplaceSearch.overviewHeader.title": "组织概览", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.description": "完成以下配置以设置您的组织。", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingHeader.title": "开始使用 Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.description": "为您的组织添加组织源,以开始搜索。", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.title": "组织源", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.description": "邀请同事加入此组织以便一同搜索。", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteFirstUsers.button": "邀请用户", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteMoreUsers.button": "邀请更多用户", - "xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.title": "用户和邀请", - "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "很好,您已邀请同事一同搜索。", - "xpack.enterpriseSearch.workplaceSearch.personalDashboard.readOnlyMode.warning": "由于常规维护,Workplace Search 当前仅可供搜索。请与您的系统管理员联系,以获取更多信息。", - "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "无法连接源,请联系管理员以获取帮助。错误消息:{error}", - "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "白金级功能", - "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "专用源需要白金级许可证。", - "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "专用源", - "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "专用源", - "xpack.enterpriseSearch.workplaceSearch.productCardDescription": "Workplace Search 为内部业务团队量身定制,将常见的生产力工具和第三方源即时连接到单个统一平台中。", - "xpack.enterpriseSearch.workplaceSearch.productCta": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.productDescription": "搜索整个虚拟工作区中存在的所有文档、文件和源。", - "xpack.enterpriseSearch.workplaceSearch.productName": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.publicKey.label": "公钥", - "xpack.enterpriseSearch.workplaceSearch.recentActivity.title": "最近活动", - "xpack.enterpriseSearch.workplaceSearch.recentActivitySourceLink.linkLabel": "查看源", - "xpack.enterpriseSearch.workplaceSearch.redirectHelp.text": "每行提供一个 URI。", - "xpack.enterpriseSearch.workplaceSearch.redirectInsecureError.text": "不推荐使用不安全的重定向 URI (http)。", - "xpack.enterpriseSearch.workplaceSearch.redirectNativeHelp.text": "对于本地开发 URI,请使用格式", - "xpack.enterpriseSearch.workplaceSearch.redirectSecureError.text": "不能包含重复的重定向 URI。", - "xpack.enterpriseSearch.workplaceSearch.redirectURIs.label": "重定向 URI", - "xpack.enterpriseSearch.workplaceSearch.remove.button": "移除", - "xpack.enterpriseSearch.workplaceSearch.removeField.label": "移除字段", - "xpack.enterpriseSearch.workplaceSearch.reset.button": "重置", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.adminRoleTypeDescription": "管理员对所有组织范围设置(包括内容源、组和用户管理功能)具有完全权限。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.allGroupsDescription": "分配给所有组包括之后创建和管理的所有当前和未来组。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.allGroupsLabel": "分配给所有组", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.defaultGroupName": "默认", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentInvalidError": "至少需要一个分配的组。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.groupAssignmentLabel": "组分配", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.roleMappingsTableHeader": "组访问权限", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.specificGroupsDescription": "静态分配给一组选定的组。", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.specificGroupsLabel": "分配给特定组", - "xpack.enterpriseSearch.workplaceSearch.roleMapping.userRoleTypeDescription": "用户的功能访问权限仅限于搜索界面和个人设置管理。", - "xpack.enterpriseSearch.workplaceSearch.roleMappingCreatedMessage": "角色映射已成功创建。", - "xpack.enterpriseSearch.workplaceSearch.roleMappingDeletedMessage": "已成功删除角色映射", - "xpack.enterpriseSearch.workplaceSearch.roleMappingUpdatedMessage": "角色映射已成功更新。", - "xpack.enterpriseSearch.workplaceSearch.saveChanges.button": "保存更改", - "xpack.enterpriseSearch.workplaceSearch.saveSettings.button": "保存设置", - "xpack.enterpriseSearch.workplaceSearch.searchableHeader": "可搜索", - "xpack.enterpriseSearch.workplaceSearch.security.privateSources.description": "专用源在您的组织中由用户连接,以创建个性化搜索体验。", - "xpack.enterpriseSearch.workplaceSearch.security.privateSourcesToggle.description": "为您的组织启用专用源", - "xpack.enterpriseSearch.workplaceSearch.security.privateSourcesUpdateConfirmation.text": "对专用源配置的更新将立即生效。", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.description": "配置后,远程专用源将{enabledStrong},用户可立即从个人仪表板上连接源。", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.enabledStrong": "默认启用", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesEmptyTable.title": "尚未配置远程专用源", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesTable.description": "远程源在磁盘上同步并存储有限数量的数据,对存储资源有很小的影响。", - "xpack.enterpriseSearch.workplaceSearch.security.remoteSourcesToggle.text": "启用远程专用源", - "xpack.enterpriseSearch.workplaceSearch.security.sourceRestrictionsSuccess.message": "已成功更新源限制。", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.description": "配置后,标准专用源{notEnabledStrong},必须先激活后,才会允许用户从个人仪表板上连接源。", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.notEnabledStrong": "默认未启用", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesEmptyTable.title": "尚未配置标准专用源", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesTable.description": "标准源在磁盘上同步并存储所有可搜索数据,对存储资源有直接相关的影响。", - "xpack.enterpriseSearch.workplaceSearch.security.standardSourcesToggle.text": "启用标准专用资源", - "xpack.enterpriseSearch.workplaceSearch.security.unsavedChanges.message": "您的专用源设置尚未保存。是否确定要离开?", - "xpack.enterpriseSearch.workplaceSearch.settings.brandText": "品牌", - "xpack.enterpriseSearch.workplaceSearch.settings.configRemoved.message": "已成功为 {name} 移除配置。", - "xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfig.message": "是否确定要移除 {name} 的配置?", - "xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfigTitle": "移除配置", - "xpack.enterpriseSearch.workplaceSearch.settings.feedbackCallOutText": "具有关于部署 {name} 连接器软件包的反馈?请告诉我们。", - "xpack.enterpriseSearch.workplaceSearch.settings.iconDescription": "用作较小尺寸屏幕和浏览器图标的品牌元素", - "xpack.enterpriseSearch.workplaceSearch.settings.iconHelpText": "最大文件大小为 2MB,建议的纵横比为 1:1。仅支持 PNG 文件。", - "xpack.enterpriseSearch.workplaceSearch.settings.iconText": "图标", - "xpack.enterpriseSearch.workplaceSearch.settings.logoDescription": "用作所有预构建搜索应用程序的主要可视化品牌元素", - "xpack.enterpriseSearch.workplaceSearch.settings.logoHelpText": "最大文件大小为 2MB。仅支持 PNG 文件。", - "xpack.enterpriseSearch.workplaceSearch.settings.logoText": "徽标", - "xpack.enterpriseSearch.workplaceSearch.settings.oauthAppUpdated.message": "已成功更新应用程序。", - "xpack.enterpriseSearch.workplaceSearch.settings.organizationLabel": "组织", - "xpack.enterpriseSearch.workplaceSearch.settings.orgUpdated.message": "已成功更新组织。", - "xpack.enterpriseSearch.workplaceSearch.settings.resetIconDescription": "您即要将图标重置为默认 Workplace Search 品牌。", - "xpack.enterpriseSearch.workplaceSearch.settings.resetImageConfirmationText": "是否确定要执行此操作?", - "xpack.enterpriseSearch.workplaceSearch.settings.resetImageTitle": "重置为默认品牌", - "xpack.enterpriseSearch.workplaceSearch.settings.resetLogoDescription": "您即要将徽标重置为默认的 Workplace Search 品牌。", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.button": "开始使用 Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.description": "将您的内容平台(Google 云端硬盘、Salesforce)整合成个性化的搜索体验。", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.imageAlt": "Workplace Search 入门 - 指导您如何开始使用 Workplace Search 的指南", - "xpack.enterpriseSearch.workplaceSearch.setupGuide.notConfigured": "Workplace Search 在 Kibana 中未配置。请按照本页上的说明执行操作。", - "xpack.enterpriseSearch.workplaceSearch.source.text": "源", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.baseUrlLabel": "基 URL", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.clientIDLabel": "客户端 ID", - "xpack.enterpriseSearch.workplaceSearch.sourceConfigFields.clientSecretLabel": "客户端密钥", - "xpack.enterpriseSearch.workplaceSearch.sourceIdentifier.sourceIdentifierFieldLabel": "源标识符", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.detailsLabel": "详情", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.reauthenticateStatusLinkLabel": "重新验证", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteLabel": "远程", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteTooltip": "远程源直接依赖于源的搜索服务,且没有内容使用 Workplace Search 进行索引。速度和结果完整性取决于第三方服务的运行状况和性能。", - "xpack.enterpriseSearch.workplaceSearch.sourceRow.searchableToggleLabel": "源可搜索切换", - "xpack.enterpriseSearch.workplaceSearch.sources.additionalConfig.heading": "需要其他配置", - "xpack.enterpriseSearch.workplaceSearch.sources.apiKeyLabel": "API 密钥", - "xpack.enterpriseSearch.workplaceSearch.sources.apiKeysTitle": "API 密钥", - "xpack.enterpriseSearch.workplaceSearch.sources.applicationLinkTitles.github": "GitHub 开发者门户", - "xpack.enterpriseSearch.workplaceSearch.sources.baseUrlTitles.github": "GitHub Enterprise URL", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedEmptyStateDescription": "添加受阻时间范围,以仅在适当时间执行同步。", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedEmptyStateTitle": "您没有受阻时间范围", - "xpack.enterpriseSearch.workplaceSearch.sources.blockedWindowsTitle": "受阻时间范围", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.accountManagement": "帐户管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.atlassian": "Atlassian", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.bugTracking": "错误跟踪", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.chat": "聊天", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.cloud": "云", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.codeRepository": "代码存储库", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.collaboration": "协作", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.communication": "通信", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.crm": "CRM", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.customerRelationshipManagement": "客户关系管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.customerService": "客户服务", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.email": "电子邮件", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.fileSharing": "文件共享", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.google": "Google", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.gsuite": "GSuite", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.help": "帮助", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.helpdesk": "帮助台", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.instantMessaging": "即时消息", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.intranet": "Intranet", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.microsoft": "Microsoft", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.office": "Office 365", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.productivity": "生产力", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.projectManagement": "项目管理", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.software": "软件", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.storage": "存储", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.ticketing": "票证", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.versionControl": "版本控制", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.wiki": "Wiki", - "xpack.enterpriseSearch.workplaceSearch.sources.categories.workflow": "工作流", - "xpack.enterpriseSearch.workplaceSearch.sources.config.link": "编辑连接器设置", - "xpack.enterpriseSearch.workplaceSearch.sources.config.title": "内容源配置", - "xpack.enterpriseSearch.workplaceSearch.sources.configuration.title": "配置", - "xpack.enterpriseSearch.workplaceSearch.sources.contentLoading.text": "正在加载内容......", - "xpack.enterpriseSearch.workplaceSearch.sources.contentSummary.title": "内容摘要", - "xpack.enterpriseSearch.workplaceSearch.sources.contentSummaryLoading.text": "正在加载摘要详情......", - "xpack.enterpriseSearch.workplaceSearch.sources.contentType.header": "内容类型", - "xpack.enterpriseSearch.workplaceSearch.sources.created.label": "创建时间:", - "xpack.enterpriseSearch.workplaceSearch.sources.customCallout.title": "开始使用定制源?", - "xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.link": "文档", - "xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.text": "在我们的{documentationLink}中详细了解如何添加内容", - "xpack.enterpriseSearch.workplaceSearch.sources.deletionSyncDescription": "移除内容源中不再存在的文档", - "xpack.enterpriseSearch.workplaceSearch.sources.deletionSyncLabel": "删除同步", - "xpack.enterpriseSearch.workplaceSearch.sources.docPermissions.description": "文档级别权限管理有关单个属性或组属性的内容访问权限。允许或拒绝对特定文档的访问。", - "xpack.enterpriseSearch.workplaceSearch.sources.documentation": "文档", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.text": "使用文档级别权限", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.title": "文档级权限", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsDisabled.text": "已为此源禁用", - "xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsLink": "详细了解文档级别权限配置", - "xpack.enterpriseSearch.workplaceSearch.sources.downloadDiagnosticButton": "下载诊断捆绑包", - "xpack.enterpriseSearch.workplaceSearch.sources.emptyActivity.title": "最近无活动", - "xpack.enterpriseSearch.workplaceSearch.sources.event.header": "事件", - "xpack.enterpriseSearch.workplaceSearch.sources.externalIdentities.link": "外部身份 API", - "xpack.enterpriseSearch.workplaceSearch.sources.externalIdentities.text": "{externalIdentitiesLink} 必须用于配置用户访问权限映射。阅读指南以了解详情。", - "xpack.enterpriseSearch.workplaceSearch.sources.feedbackCallOutText": "具有关于部署连接器软件包的反馈?请告诉我们。", - "xpack.enterpriseSearch.workplaceSearch.sources.feedbackLinkLabel": "具有关于部署 {name} 连接器的反馈?请告诉我们。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.additionalConfigurationNeeded": "此源需要其他配置。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConfigUpdated": "已成功更新配置。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConnected": "已成功连接 {sourceName}。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceNameChanged": "已成功将名称更改为 {sourceName}。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceRemoved": "已成功删除 {sourceName}。", - "xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.externalConnectorCreated": "已成功注册连接器软件包部署。", - "xpack.enterpriseSearch.workplaceSearch.sources.fullSyncDescription": "从内容源检索所有可能的文档", - "xpack.enterpriseSearch.workplaceSearch.sources.fullSyncLabel": "完全同步", - "xpack.enterpriseSearch.workplaceSearch.sources.groupAccess.title": "组访问权限", - "xpack.enterpriseSearch.workplaceSearch.sources.helpText.custom": "要创建定制 API 源,请提供可人工读取的描述性名称。名称在各种搜索体验和管理界面中都原样显示。", - "xpack.enterpriseSearch.workplaceSearch.sources.id.label": "源标识符", - "xpack.enterpriseSearch.workplaceSearch.sources.incrementalSyncDescription": "检索自上次同步作业以来的文档/更新", - "xpack.enterpriseSearch.workplaceSearch.sources.incrementalSyncLabel": "增量同步", - "xpack.enterpriseSearch.workplaceSearch.sources.indexingRulesTable.includeEverythingMessage": "包括来自此源的所有其他内容", - "xpack.enterpriseSearch.workplaceSearch.sources.items.header": "项", - "xpack.enterpriseSearch.workplaceSearch.sources.learnCustom.features.button": "了解白金级功能", - "xpack.enterpriseSearch.workplaceSearch.sources.learnMore.link": "了解详情", - "xpack.enterpriseSearch.workplaceSearch.sources.learnMore.text": "{learnMoreLink}的权限", - "xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.description": "请与您的搜索体验管理员联系,以获取更多信息。", - "xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.title": "专用源不再可用", - "xpack.enterpriseSearch.workplaceSearch.sources.nextSyncRunningMessage": "一旦当前运行的作业完成", - "xpack.enterpriseSearch.workplaceSearch.sources.noContent.title": "尚没有任何内容", - "xpack.enterpriseSearch.workplaceSearch.sources.noContentEmpty.message": "此源尚没有任何内容", - "xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage": "未找到源。", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.accounts": "帐户", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.allFiles": "所有文件(包括图像、PDF、电子表格、文本文档和演示)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.allStoredFiles": "所有已存储文件(包括图像、视频、PDF、电子表格、文本文档和演示)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.articles": "文章", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.attachments": "附件", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.blogPosts": "博文", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.bugs": "错误", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.campaigns": "营销活动", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.cases": "案例(包括种子和注释)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.contacts": "联系人", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.directMessages": "私信", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.emails": "电子邮件", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.epics": "长篇故事", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.files": "文件(仅限 Markdown)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.folders": "文件夹", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.gSuiteFiles": "Google G Suite 文档(文档、表格、幻灯片)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.incidents": "事件", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.issues": "问题(包括注释)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.items": "项", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.leads": "线索", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.opportunities": "机会", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.pages": "页面", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.privateMessages": "您积极参与的私人频道的消息", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.projects": "项目", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.publicMessages": "公共频道消息", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.pullRequests": "Pull 请求(包括注释)", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.repositoryList": "存储库列表", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.sites": "网站", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.spaces": "工作区", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.stories": "故事", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.tasks": "任务", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.tickets": "工单", - "xpack.enterpriseSearch.workplaceSearch.sources.objTypes.users": "用户", - "xpack.enterpriseSearch.workplaceSearch.sources.org.description": "组织源可供整个组织使用,并可以分配给特定用户组。", - "xpack.enterpriseSearch.workplaceSearch.sources.org.empty.description": "内容源共享给您后,其将显示在此处,并可通过搜索体验获取。", - "xpack.enterpriseSearch.workplaceSearch.sources.org.empty.title": "没有可用的内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.org.link": "添加组织内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.org.title": "组织源", - "xpack.enterpriseSearch.workplaceSearch.sources.permissionsSyncDescription": "检索自上次同步作业以来的所有权限更改", - "xpack.enterpriseSearch.workplaceSearch.sources.permissionsSyncLabel": "权限同步", - "xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.description": "查看所有已连接专用源的状态,以及为您的帐户管理专用源。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.title": "管理专用内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.empty.title": "您没有专用源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "专用源仅供您使用。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "我的专用内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "添加专用内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateOrg.header.description": "您可以通过{groups, plural, other {组}} {groupsSentence}{newline}访问以下源。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateOrg.header.title": "组织内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "查看与您的组共享的所有源的状态。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "查看组源", - "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "可供搜索", - "xpack.enterpriseSearch.workplaceSearch.sources.remoteSource.label": "远程源", - "xpack.enterpriseSearch.workplaceSearch.sources.remove.description": "此操作无法撤消。", - "xpack.enterpriseSearch.workplaceSearch.sources.remove.title": "移除此内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.description": "定制此内容源的名称。", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "设置", - "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "内容源名称", - "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "将从 Workplace Search 中删除您的源文档。{lineBreak}确定要移除 {name}?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAddRuleLabel": "添加索引规则", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsAssetsLabel": "资产", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsDescription": "灵活管理要同步的文档并使用下面的粒度控件提供搜索功能。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsLearnMoreLink": "详细了解同步对象类型。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsDescription": "添加索引规则以定制要从 {contentSourceName} 同步哪些数据。默认包括所有内容,并根据从上到下列出的已配置索引规则集验证文档。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsLabel": "对象", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableExcludeLabel": "排除", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableFileLabel": "文件类型", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableIncludeLabel": "包括", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableItemLabel": "项目", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePathLabel": "路径", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTablePolicyLabel": "策略", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableRuleLabel": "规则", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsObjectsTableValueLabel": "值", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceAssetsAndObjectsSyncLearnMoreLink": "详细了解如何定制索引规则。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceBlockedTimeWindowsLinkLabel": "受阻时间范围", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.filter.placeholder": "筛选内容......", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.search.placeholder": "搜索内容......", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "源内容", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "了解白金级许可证", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "您的组织的许可证级别已更改。您的数据是安全的,但不再支持文档级别权限,且已禁止搜索此源。升级到白金级许可证,以重新启用此源。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.title": "内容源已禁用", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceFrequencyDescription": "管理从 Workplace Search 到此内容源的数据同步频率。提高同步频率,以确保数据保持最新。降低同步频率,以减轻第三方服务器的负担。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceName.label": "源名称", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.box": "Box", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluence": "Confluence", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluenceConnectorPackage": "Confluence 连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluenceServer": "Confluence(服务器)", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.custom": "定制 API 源", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.customConnectorPackage": "定制连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.dropbox": "Dropbox", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.github": "GitHub", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.githubEnterprise": "GitHub Enterprise Server", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.gmail": "Gmail", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.googleDrive": "Google 云端硬盘", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.jira": "Jira", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.jiraServer": "Jira(服务器)", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.networkDrive": "网络驱动器", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.oneDrive": "OneDrive", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.outlook": "Outlook", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.salesforce": "Salesforce", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.salesforceSandbox": "Salesforce Sandbox", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.serviceNow": "ServiceNow", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePoint": "Sharepoint", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePointConnectorPackage": "SharePoint Online 连接器软件包", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.sharePointServer": "SharePoint Server", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.slack": "Slack", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.teams": "Teams", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zendesk": "Zendesk", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.zoom": "缩放", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceOverviewTitle": "源概览", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSettings.pemKeyPrompText": "上传新的 .pem 文件以轮换私钥", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmMessage": "是否确定要继续处理此请求并停止所有其他同步?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncConfirmTitle": "开始新内容同步?", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncFrequencyLinkLabel": "同步频率", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSyncFrequencyTitle": "同步频率", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationButtonLabel": "同步内容", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationDescription": "启用或禁用从该内容源到 Workplace Search 的数据同步。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationFrequencyTitle": "同步频率", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationTitle": "同步", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleDescription": "源内容将自动保持同步。", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceSynchronizationToggleLabel": "同步此源", - "xpack.enterpriseSearch.workplaceSearch.sources.status.header": "状态", - "xpack.enterpriseSearch.workplaceSearch.sources.status.heading": "所有都看起来不错", - "xpack.enterpriseSearch.workplaceSearch.sources.status.label": "状态:", - "xpack.enterpriseSearch.workplaceSearch.sources.status.text": "您的终端已准备好接受请求。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsButton": "下载诊断数据", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsDescription": "检索用于活动同步流程故障排除的相关诊断数据。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncDiagnosticsTitle": "同步诊断", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationCallout": "配置{syncFrequencyLink}或{blockTimeWindowsLink}。", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationDisabledDescription": "请联系您的管理员以启用同步控件。", - "xpack.enterpriseSearch.workplaceSearch.sources.synchronizationDisabledTitle": "已禁用源同步。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncSettingsUpdatedMessage": "已更新源同步设置。", - "xpack.enterpriseSearch.workplaceSearch.sources.syncUnsavedChangesMessage": "您的更改尚未更改。是否确定要离开?", - "xpack.enterpriseSearch.workplaceSearch.sources.time.header": "时间", - "xpack.enterpriseSearch.workplaceSearch.sources.totalDocuments.label": "总文档数", - "xpack.enterpriseSearch.workplaceSearch.sources.understandButton": "我理解", - "xpack.enterpriseSearch.workplaceSearch.sources.utcLabel": "当前 UTC 时间:{utcTime}", - "xpack.enterpriseSearch.workplaceSearch.sources.utcTitle": "所有时间均为 UTC 格式", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addFirstSources.button": "添加源", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addMoreSources.button": "添加更多源", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description": "您已添加 {sourcesCount, number} 个组织{sourcesCount, plural, other {源}}。祝您搜索愉快。", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.docPermissions.description": "只有在配置用户和组映射后,才能在 Workplace Search 中搜索文档。{documentPermissionsLink}。", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading": "{addedSourceName} 需要其他配置。", - "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.success": "{addedSourceName} 已成功连接,初始内容同步已在进行中。因为您选择同步文档级别权限信息,所以现在必须使用 {externalIdentitiesLink} 提供用户和组映射。", - "xpack.enterpriseSearch.workplaceSearch.statusPopoverTooltip": "单击以查看信息", - "xpack.enterpriseSearch.workplaceSearch.title": "Workplace Search", - "xpack.enterpriseSearch.workplaceSearch.update.label": "更新", - "xpack.enterpriseSearch.workplaceSearch.url.label": "URL", "xpack.eventLog.savedObjectProviderRegistry.getProvidersClient.noDefaultProvider": "事件日志需要默认提供程序。", "xpack.exploratoryView..synthetics.addDataButtonLabel": "添加 Synthetics 数据", "xpack.exploratoryView.alerts.alertStarted": "时间戳", @@ -31585,17 +29846,6 @@ "xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName": "未分配", "xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel": "未分配主分片", "xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel": "未分配副本分片", - "xpack.monitoring.entSearch.overview.appSearchEngines": "App Search 引擎", - "xpack.monitoring.entSearch.overview.appSearchSummary": "App Search 摘要", - "xpack.monitoring.entSearch.overview.heading": "Enterprise Search 概览", - "xpack.monitoring.entSearch.overview.instances": "实例", - "xpack.monitoring.entSearch.overview.lowLevelSummary": "低级别资源使用率摘要", - "xpack.monitoring.entSearch.overview.networkingSummary": "网络流量摘要", - "xpack.monitoring.entSearch.overview.pageTitle": "Enterprise Search 概览", - "xpack.monitoring.entSearch.overview.routeTitle": "Enterprise Search - 概览", - "xpack.monitoring.entSearch.overview.workplaceSearchOrgSources": "组织内容源", - "xpack.monitoring.entSearch.overview.workplaceSearchPrivateSources": "专用内容源", - "xpack.monitoring.entSearch.overview.workplaceSearchSummary": "Workplace Search 摘要", "xpack.monitoring.errors.connectionErrorMessage": "连接错误:检查 Elasticsearch Monitoring 集群网络连接,并参考 Kibana 日志以了解详情。", "xpack.monitoring.errors.insufficientUserErrorMessage": "对监测数据没有足够的用户权限", "xpack.monitoring.errors.invalidAuthErrorMessage": "监测集群的身份验证无效", 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 5b6b847643206..510a8a2ed06cc 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 @@ -6,68 +6,12 @@ */ export const DEFAULT_INITIAL_APP_DATA = { - kibanaVersion: '7.16.0', - enterpriseSearchVersion: '7.16.0', - readOnlyMode: false, - searchOAuth: { - clientId: 'someUID', - redirectUrl: 'http://localhost:3002/ws/search_callback', - }, - configuredLimits: { - appSearch: { - engine: { - maxDocumentByteSize: 102400, - maxEnginesPerMetaEngine: 15, - }, - }, - workplaceSearch: { - customApiSource: { - maxDocumentByteSize: 102400, - totalFields: 64, - }, - }, - }, - access: { - hasAppSearchAccess: true, - hasWorkplaceSearchAccess: true, - }, features: { hasConnectors: true, hasDefaultIngestPipeline: true, hasDocumentLevelSecurityEnabled: true, hasIncrementalSyncEnabled: true, hasNativeConnectors: true, - hasWebCrawler: true, - }, - appSearch: { - accountId: 'some-id-string', - kibanaUIsEnabled: true, - onboardingComplete: true, - role: { - id: 'account_id:somestring|user_oid:somestring', - roleType: 'owner', - ability: { - accessAllEngines: true, - manage: ['account_credentials', 'account_engines'], // etc - edit: ['LocoMoco::Account'], // etc - view: ['Engine'], // etc - credentialTypes: ['admin', 'private', 'search'], - availableRoleTypes: ['owner', 'admin'], - }, - }, - }, - workplaceSearch: { - organization: { - name: 'ACME Donuts', - defaultOrgName: 'My Organization', - kibanaUIsEnabled: false, - }, - account: { - id: 'some-id-string', - groups: ['Default', 'Cats'], - isAdmin: true, - canCreatePrivateSources: true, - viewedOnboardingPage: true, - }, }, + kibanaVersion: '9.0.0', }; diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 8e69b80564802..222396dde5cea 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -12,8 +12,6 @@ import { ENTERPRISE_SEARCH_CONTENT_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, - ENTERPRISE_SEARCH_APPSEARCH_APP_ID, - ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID, SEARCH_ELASTICSEARCH, SEARCH_VECTOR_SEARCH, SEARCH_SEMANTIC_SEARCH, @@ -105,33 +103,6 @@ export const ELASTICSEARCH_PLUGIN = { SUPPORT_URL: 'https://discuss.elastic.co/c/elastic-stack/elasticsearch/', }; -export const APP_SEARCH_PLUGIN = { - ID: ENTERPRISE_SEARCH_APPSEARCH_APP_ID, - NAME: i18n.translate('xpack.enterpriseSearch.appSearch.productName', { - defaultMessage: 'App Search', - }), - DESCRIPTION: i18n.translate('xpack.enterpriseSearch.appSearch.productDescription', { - defaultMessage: - 'Leverage dashboards, analytics, and APIs for advanced application search made simple.', - }), - URL: '/app/enterprise_search/app_search', - SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/app-search/', -}; - -export const WORKPLACE_SEARCH_PLUGIN = { - ID: ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID, - NAME: i18n.translate('xpack.enterpriseSearch.workplaceSearch.productName', { - defaultMessage: 'Workplace Search', - }), - DESCRIPTION: i18n.translate('xpack.enterpriseSearch.workplaceSearch.productDescription', { - defaultMessage: - 'Search all documents, files, and sources available across your virtual workplace.', - }), - URL: '/app/enterprise_search/workplace_search', - NON_ADMIN_URL: '/app/enterprise_search/workplace_search/p', - SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/workplace-search/', -}; - export const SEARCH_EXPERIENCES_PLUGIN = { ID: 'searchExperiences', NAME: i18n.translate('xpack.enterpriseSearch.searchExperiences.productName', { @@ -148,8 +119,6 @@ export const SEARCH_EXPERIENCES_PLUGIN = { GITHUB_URL: 'https://github.com/elastic/search-ui/', DOCUMENTATION_URL: 'https://docs.elastic.co/search-ui/', ELASTICSEARCH_TUTORIAL_URL: 'https://docs.elastic.co/search-ui/tutorials/elasticsearch', - APP_SEARCH_TUTORIAL_URL: 'https://docs.elastic.co/search-ui/tutorials/app-search', - WORKPLACE_SEARCH_TUTORIAL_URL: 'https://docs.elastic.co/search-ui/tutorials/workplace-search', }; export const APPLICATIONS_PLUGIN = { @@ -269,7 +238,6 @@ export const DEFAULT_PRODUCT_FEATURES: ProductFeatures = { hasDocumentLevelSecurityEnabled: true, hasIncrementalSyncEnabled: true, hasNativeConnectors: true, - hasWebCrawler: true, }; export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-'; diff --git a/x-pack/plugins/enterprise_search/common/guided_onboarding/search_guide_config.ts b/x-pack/plugins/enterprise_search/common/guided_onboarding/search_guide_config.ts index d46ab70628fd1..5d6c5e9f11dfd 100644 --- a/x-pack/plugins/enterprise_search/common/guided_onboarding/search_guide_config.ts +++ b/x-pack/plugins/enterprise_search/common/guided_onboarding/search_guide_config.ts @@ -8,20 +8,15 @@ import type { GuideConfig, StepConfig } from '@kbn/guided-onboarding'; import { i18n } from '@kbn/i18n'; -export const appSearchGuideId = 'appSearch'; export const websiteSearchGuideId = 'websiteSearch'; export const databaseSearchGuideId = 'databaseSearch'; const apiRoutes = { - [appSearchGuideId]: '/search_indices/new_index/api', [databaseSearchGuideId]: '/connectors/select_connector', [websiteSearchGuideId]: '/crawlers', }; -export type EnterpriseSearchGuideIds = - | typeof appSearchGuideId - | typeof websiteSearchGuideId - | typeof databaseSearchGuideId; +export type EnterpriseSearchGuideIds = typeof websiteSearchGuideId | typeof databaseSearchGuideId; const getAddDataStep: (method?: EnterpriseSearchGuideIds) => StepConfig = (method) => { return { @@ -88,8 +83,6 @@ const getGuideConfig: (telemetryId: EnterpriseSearchGuideIds) => GuideConfig = ( }; }; -export const appSearchGuideConfig: GuideConfig = getGuideConfig(appSearchGuideId); - export const websiteSearchGuideConfig: GuideConfig = getGuideConfig(websiteSearchGuideId); export const databaseSearchGuideConfig: GuideConfig = getGuideConfig(databaseSearchGuideId); diff --git a/x-pack/plugins/enterprise_search/common/jest.config.js b/x-pack/plugins/enterprise_search/common/jest.config.js index b43a5fefdc920..f637b3ad8667b 100644 --- a/x-pack/plugins/enterprise_search/common/jest.config.js +++ b/x-pack/plugins/enterprise_search/common/jest.config.js @@ -18,8 +18,4 @@ module.exports = { '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/common/types/app_search.ts b/x-pack/plugins/enterprise_search/common/types/app_search.ts deleted file mode 100644 index cb5e2ec46f04d..0000000000000 --- a/x-pack/plugins/enterprise_search/common/types/app_search.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 interface Account { - accountId: string; - kibanaUIsEnabled: boolean; - onboardingComplete: boolean; - role: { - id: string; - roleType: string; - ability: { - accessAllEngines: boolean; - manage: string[]; - edit: string[]; - view: string[]; - credentialTypes: string[]; - availableRoleTypes: string[]; - }; - }; -} - -export interface ConfiguredLimits { - engine: { - maxDocumentByteSize: number; - maxEnginesPerMetaEngine: number; - }; -} diff --git a/x-pack/plugins/enterprise_search/common/types/index.ts b/x-pack/plugins/enterprise_search/common/types/index.ts index 364008ef18b49..2a05a8e7bda23 100644 --- a/x-pack/plugins/enterprise_search/common/types/index.ts +++ b/x-pack/plugins/enterprise_search/common/types/index.ts @@ -5,35 +5,9 @@ * 2.0. */ -import { - Account as AppSearchAccount, - ConfiguredLimits as AppSearchConfiguredLimits, -} from './app_search'; -import { - WorkplaceSearchInitialData, - ConfiguredLimits as WorkplaceSearchConfiguredLimits, -} from './workplace_search'; - export interface InitialAppData { - access?: ProductAccess; - appSearch?: AppSearchAccount; - configuredLimits?: ConfiguredLimits; - enterpriseSearchVersion?: string; features?: ProductFeatures; kibanaVersion?: string; - readOnlyMode?: boolean; - searchOAuth?: SearchOAuth; - workplaceSearch?: WorkplaceSearchInitialData; -} - -export interface ConfiguredLimits { - appSearch: AppSearchConfiguredLimits; - workplaceSearch: WorkplaceSearchConfiguredLimits; -} - -export interface ProductAccess { - hasAppSearchAccess: boolean; - hasWorkplaceSearchAccess: boolean; } export interface ProductFeatures { @@ -42,7 +16,6 @@ export interface ProductFeatures { hasDocumentLevelSecurityEnabled: boolean; hasIncrementalSyncEnabled: boolean; hasNativeConnectors: boolean; - hasWebCrawler: boolean; } export interface SearchOAuth { diff --git a/x-pack/plugins/enterprise_search/common/types/workplace_search.ts b/x-pack/plugins/enterprise_search/common/types/workplace_search.ts deleted file mode 100644 index 68643436235bb..0000000000000 --- a/x-pack/plugins/enterprise_search/common/types/workplace_search.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 interface Account { - id: string; - groups: string[]; - isAdmin: boolean; - canCreatePrivateSources: boolean; - viewedOnboardingPage: boolean; -} - -export interface Organization { - defaultOrgName: string; - kibanaUIsEnabled: boolean; - name: string; -} - -export interface WorkplaceSearchInitialData { - account: Account; - organization: Organization; -} - -export interface ConfiguredLimits { - customApiSource: { - maxDocumentByteSize: number; - totalFields: number; - }; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index 4dde6a5be527e..63cba3759f568 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -54,15 +54,10 @@ export const mockKibanaValues = { }), } as unknown as LensPublicStart, navigateToUrl: jest.fn(), - productAccess: { - hasAppSearchAccess: true, - hasWorkplaceSearchAccess: true, - }, productFeatures: { hasDocumentLevelSecurityEnabled: true, hasIncrementalSyncEnabled: true, hasNativeConnectors: true, - hasWebCrawler: true, }, renderHeaderActions: jest.fn(), security: securityMock.createStart(), diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/telemetry_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/telemetry_logic.mock.ts index fbc1537e733e7..377670bfe2430 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/telemetry_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/telemetry_logic.mock.ts @@ -9,7 +9,6 @@ export const mockTelemetryActions = { sendTelemetry: jest.fn(), sendEnterpriseSearchTelemetry: jest.fn(), sendAppSearchTelemetry: jest.fn(), - sendWorkplaceSearchTelemetry: jest.fn(), }; jest.mock('../../shared/telemetry', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/ai_search/jest.config.js index f59def2d72fd6..7178943ad8047 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/ai_search', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/analytics/jest.config.js index 58b5334165ec5..53873f821830e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/analytics', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_creation_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_creation_logic.mock.ts deleted file mode 100644 index 11fb075a6ca55..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_creation_logic.mock.ts +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 dedent from 'dedent'; - -import { ElasticsearchIndexWithPrivileges } from '../../../../common/types/indices'; - -import { EngineCreationSteps } from '../components/engine_creation/engine_creation_logic'; -import { SearchIndexSelectableOption } from '../components/engine_creation/search_index_selectable'; - -export const DEFAULT_VALUES = { - ingestionMethod: '', - isLoading: false, - name: '', - rawName: '', - language: 'Universal', - isLoadingIndices: false, - indices: [], - indicesFormatted: [], - selectedIndex: '', - engineType: 'appSearch', - isSubmitDisabled: true, - aliasName: '', - aliasRawName: '', - isAliasAllowed: true, - isAliasRequired: false, - currentEngineCreationStep: EngineCreationSteps.SelectStep, - aliasNameErrorMessage: '', - showAliasNameErrorMessages: false, - selectedIndexFormatted: undefined, -}; - -export const mockElasticsearchIndices: ElasticsearchIndexWithPrivileges[] = [ - { - count: 0, - health: 'yellow', - hidden: false, - status: 'open', - name: 'search-my-index-1', - uuid: 'ydlR_QQJTeyZP66tzQSmMQ', - alias: false, - privileges: { read: true, manage: true }, - total: { - docs: { - count: 0, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - health: 'green', - hidden: false, - status: 'open', - name: 'my-index-2', - uuid: '4dlR_QQJTe2ZP6qtzQSmMQ', - alias: false, - privileges: { read: true, manage: true }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - health: 'green', - hidden: false, - status: 'open', - name: 'search-my-index-2', - uuid: '4dlR_QQJTe2ZP6qtzQSmMQ', - alias: true, - privileges: { read: true, manage: true }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - health: 'green', - hidden: false, - status: 'open', - name: 'alias-my-index-2', - uuid: '4dlR_QQJTe2ZP6qtzQSmMQ', - alias: true, - privileges: { read: true, manage: true }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - health: 'green', - hidden: false, - status: 'open', - name: 'index-without-read-privilege', - uuid: '4dlR_QQJTe2ZP6qtzQSmMQ', - alias: false, - privileges: { read: false, manage: true }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - health: 'green', - hidden: false, - status: 'open', - name: 'index-without-manage-privilege', - uuid: '4dlR_QQJTe2ZP6qtzQSmMQ', - alias: false, - privileges: { read: true, manage: false }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - health: 'green', - hidden: false, - status: 'open', - name: 'alias-without-manage-privilege', - uuid: '4dlR_QQJTe2ZP6qtzQSmMQ', - alias: true, - privileges: { read: true, manage: false }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, -]; - -export const mockSearchIndexOptions: SearchIndexSelectableOption[] = [ - { - count: 0, - label: 'search-my-index-1', - health: 'yellow', - status: 'open', - alias: false, - disabled: false, - badge: { - color: 'success', - label: 'Index', - toolTipTitle: 'Index name is compatible', - toolTipContent: dedent(` - You can directly use this index. You can also optionally create an - alias to use as the source of the engine instead. - `), - }, - total: { - docs: { - count: 0, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - label: 'my-index-2', - health: 'green', - status: 'open', - alias: false, - disabled: false, - badge: { - color: 'warning', - label: 'Index', - icon: 'iInCircle', - toolTipTitle: 'Index name is incompatible', - toolTipContent: dedent(` - Enterprise Search will automatically create an alias to use as the - source of the search engine rather than use this index directly. - `), - }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - label: 'search-my-index-2', - health: 'green', - status: 'open', - alias: true, - disabled: false, - badge: { - color: 'success', - label: 'Alias', - toolTipTitle: 'Alias is compatible', - toolTipContent: 'You can use this alias.', - }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, - { - count: 100, - label: 'alias-my-index-2', - health: 'green', - status: 'open', - alias: true, - disabled: true, - badge: { - color: 'danger', - label: 'Alias', - icon: 'warning', - toolTipTitle: 'Alias name is incompatible', - toolTipContent: 'You\'ll have to create a new alias prefixed with "search-".', - }, - total: { - docs: { - count: 100, - deleted: 0, - }, - store: { - size_in_bytes: '225b', - }, - }, - }, -]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts deleted file mode 100644 index b38659b7a9a79..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { EngineDetails } from '../components/engine/types'; -import { ENGINES_TITLE } from '../components/engines/constants'; -import { generateEncodedPath } from '../utils/encode_path_params'; - -export const mockEngineValues = { - engineName: 'some-engine', - engine: {} as EngineDetails, - searchKey: 'search-abc123', -}; - -export const mockEngineActions = { - initializeEngine: jest.fn(), -}; - -export const mockGenerateEnginePath = jest.fn((path, pathParams = {}) => - generateEncodedPath(path, { engineName: mockEngineValues.engineName, ...pathParams }) -); -export const mockGetEngineBreadcrumbs = jest.fn((breadcrumbs = []) => [ - ENGINES_TITLE, - mockEngineValues.engineName, - ...breadcrumbs, -]); - -jest.mock('../components/engine', () => ({ - EngineLogic: { - values: mockEngineValues, - actions: mockEngineActions, - }, - generateEnginePath: mockGenerateEnginePath, - getEngineBreadcrumbs: mockGetEngineBreadcrumbs, -})); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engines.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engines.mock.ts deleted file mode 100644 index 4076af058d6b7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engines.mock.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { EngineTypes } from '../components/engine/types'; - -export const defaultEngine = { - id: 'e1', - name: 'engine1', - type: EngineTypes.default, - language: null, - result_fields: {}, -}; - -export const indexedEngine = { - id: 'e2', - name: 'engine2', - type: EngineTypes.indexed, - language: null, - result_fields: {}, -}; - -export const engines = [defaultEngine, indexedEngine]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/index.ts deleted file mode 100644 index b444c1cc94383..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { mockEngineValues, mockEngineActions } from './engine_logic.mock'; -export { mockRecursivelyFetchEngines, mockSourceEngines } from './recursively_fetch_engines.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/recursively_fetch_engines.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/recursively_fetch_engines.mock.ts deleted file mode 100644 index dd4c86a2a6360..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/recursively_fetch_engines.mock.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { EngineDetails } from '../components/engine/types'; - -export const mockSourceEngines = [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, -] as EngineDetails[]; - -export const mockRecursivelyFetchEngines = jest.fn(({ onComplete }) => - onComplete(mockSourceEngines) -); - -jest.mock('../utils/recursively_fetch_engines', () => ({ - recursivelyFetchEngines: mockRecursivelyFetchEngines, -})); 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 deleted file mode 100644 index 35dafbeb33178..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__'; -import { LogicMounter } from '../__mocks__/kea_logic/logic_mounter.test_helper'; - -jest.mock('../shared/licensing', () => ({ - LicensingLogic: { selectors: { hasPlatinumLicense: () => false } }, -})); - -import { AppLogic } from './app_logic'; - -describe('AppLogic', () => { - const { mount } = new LogicMounter(AppLogic); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const DEFAULT_VALUES = { - configuredLimits: { - engine: { - maxDocumentByteSize: 102400, - maxEnginesPerMetaEngine: 15, - }, - }, - account: { - accountId: 'some-id-string', - kibanaUIsEnabled: true, - onboardingComplete: true, - role: DEFAULT_INITIAL_APP_DATA.appSearch.role, - }, - myRole: {}, - showGateForm: false, - }; - - it('sets values from props', () => { - mount({}, DEFAULT_INITIAL_APP_DATA); - - expect(AppLogic.values).toEqual({ - configuredLimits: { - engine: { - maxDocumentByteSize: 102400, - maxEnginesPerMetaEngine: 15, - }, - }, - account: { - accountId: 'some-id-string', - kibanaUIsEnabled: true, - onboardingComplete: true, - role: DEFAULT_INITIAL_APP_DATA.appSearch.role, - }, - myRole: expect.objectContaining({ - id: 'account_id:somestring|user_oid:somestring', - roleType: 'owner', - availableRoleTypes: ['owner', 'admin'], - credentialTypes: ['admin', 'private', 'search'], - canAccessAllEngines: true, - canViewAccountCredentials: true, - // Truncated for brevity - see utils/role/index.test.ts for full output - }), - showGateForm: false, - }); - }); - - describe('actions', () => { - describe('setOnboardingComplete()', () => { - it('sets true', () => { - mount({}, { ...DEFAULT_INITIAL_APP_DATA, appSearch: { onboardingComplete: false } }); - - AppLogic.actions.setOnboardingComplete(); - expect(AppLogic.values).toEqual({ - ...DEFAULT_VALUES, - account: { - onboardingComplete: true, - }, - }); - }); - }); - }); - - describe('selectors', () => { - describe('myRole', () => { - it('falls back to an empty object if role is missing', () => { - mount({}, { ...DEFAULT_INITIAL_APP_DATA, appSearch: {} }); - - expect(AppLogic.values).toEqual({ - ...DEFAULT_VALUES, - account: {}, - myRole: {}, - }); - }); - }); - }); -}); 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 deleted file mode 100644 index 26187e16f05a9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { InitialAppData } from '../../../common/types'; - -import { LicensingLogic } from '../shared/licensing'; - -import { ConfiguredLimits, Account, Role } from './types'; - -import { getRoleAbilities } from './utils/role'; - -interface AppValues { - account: Account; - showGateForm: boolean; - configuredLimits: ConfiguredLimits; - myRole: Role; -} - -interface AppActions { - setOnboardingComplete(): boolean; -} - -export const AppLogic = kea>>({ - path: ['enterprise_search', 'app_search', 'app_logic'], - actions: { - setOnboardingComplete: () => true, - }, - reducers: ({ props }) => ({ - account: [ - props.appSearch, - { - // @ts-expect-error upgrade typescript v5.1.6 - setOnboardingComplete: (account) => ({ - ...account, - onboardingComplete: true, - }), - }, - ], - configuredLimits: [props.configuredLimits.appSearch, {}], - showGateForm: [ - props.appSearch.kibanaUIsEnabled === false && props.appSearch.role.roleType === 'owner', - {}, - ], - }), - selectors: { - myRole: [ - (selectors) => [selectors.account, LicensingLogic.selectors.hasPlatinumLicense], - ({ role }) => (role ? getRoleAbilities(role) : {}), - ], - }, -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx deleted file mode 100644 index 280282a2fc6ec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 '../../../__mocks__/shallow_useeffect.mock'; -import { mockKibanaValues, setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../__mocks__/react_router'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { - rerender, - getPageTitle, - getPageHeaderActions, - getPageHeaderChildren, -} from '../../../test_helpers'; -import { LogRetentionTooltip, LogRetentionCallout } from '../log_retention'; - -import { AnalyticsLayout } from './analytics_layout'; -import { AnalyticsFilters } from './components'; - -describe('AnalyticsLayout', () => { - const { history } = mockKibanaValues; - - const values = { - history, - dataLoading: false, - }; - const actions = { - loadAnalyticsData: jest.fn(), - loadQueryData: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - history.location.search = ''; - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow( - -
World!
-
- ); - - expect(wrapper.find(LogRetentionCallout)).toHaveLength(1); - expect(getPageHeaderActions(wrapper).find(LogRetentionTooltip)).toHaveLength(1); - expect(getPageHeaderChildren(wrapper).find(AnalyticsFilters)).toHaveLength(1); - - expect(getPageTitle(wrapper)).toEqual('Hello'); - expect(wrapper.find('[data-test-subj="world"]').text()).toEqual('World!'); - - expect(wrapper.prop('pageChrome')).toEqual(['Engines', 'some-engine', 'Analytics']); - }); - - it('passes analytics breadcrumbs', () => { - const wrapper = shallow(); - - expect(wrapper.prop('pageChrome')).toEqual(['Engines', 'some-engine', 'Analytics', 'Queries']); - }); - - describe('data loading', () => { - it('loads query data for query details pages', () => { - mockUseParams.mockReturnValueOnce({ query: 'test' }); - shallow(); - - expect(actions.loadQueryData).toHaveBeenCalledWith('test'); - }); - - it('loads analytics data for non query details pages', () => { - shallow(); - - expect(actions.loadAnalyticsData).toHaveBeenCalled(); - }); - - it('reloads data when search params are updated (by our AnalyticsHeader filters)', () => { - const wrapper = shallow(); - expect(actions.loadAnalyticsData).toHaveBeenCalledTimes(1); - - history.location.search = '?tag=some-filter'; - rerender(wrapper); - - expect(actions.loadAnalyticsData).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx deleted file mode 100644 index a88c7f24b6281..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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, { useEffect, FC, PropsWithChildren } from 'react'; -import { useParams } from 'react-router-dom'; - -import { useValues, useActions } from 'kea'; - -import { KibanaLogic } from '../../../shared/kibana'; -import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs'; -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { LogRetentionTooltip, LogRetentionCallout, LogRetentionOptions } from '../log_retention'; - -import { AnalyticsFilters } from './components'; -import { ANALYTICS_TITLE } from './constants'; - -import { AnalyticsLogic } from '.'; - -interface Props { - title: string; - breadcrumbs?: BreadcrumbTrail; - isQueryView?: boolean; - isAnalyticsView?: boolean; -} -export const AnalyticsLayout: FC> = ({ - title, - breadcrumbs = [], - isQueryView, - isAnalyticsView, - children, -}) => { - const { history } = useValues(KibanaLogic); - const { query } = useParams() as { query: string }; - const { dataLoading } = useValues(AnalyticsLogic); - const { loadAnalyticsData, loadQueryData } = useActions(AnalyticsLogic); - - useEffect(() => { - if (isQueryView) loadQueryData(query); - if (isAnalyticsView) loadAnalyticsData(); - }, [history.location.search]); - - return ( - , - ], - children: , - responsive: false, - }} - isLoading={dataLoading} - > - - {children} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts deleted file mode 100644 index cf0d36ef3e95d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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, mockKibanaValues, mockHttpValues } from '../../../__mocks__/kea_logic'; - -jest.mock('../engine', () => ({ - EngineLogic: { values: { engineName: 'test-engine' } }, -})); - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { DEFAULT_START_DATE, DEFAULT_END_DATE } from './constants'; - -import { AnalyticsLogic } from '.'; - -describe('AnalyticsLogic', () => { - const { mount } = new LogicMounter(AnalyticsLogic); - const { history } = mockKibanaValues; - const { http } = mockHttpValues; - - const DEFAULT_VALUES = { - dataLoading: true, - allTags: [], - recentQueries: [], - topQueries: [], - topQueriesNoResults: [], - topQueriesNoClicks: [], - topQueriesWithClicks: [], - totalQueries: 0, - totalQueriesNoResults: 0, - totalClicks: 0, - totalQueriesForQuery: 0, - queriesPerDay: [], - queriesNoResultsPerDay: [], - clicksPerDay: [], - queriesPerDayForQuery: [], - topClicksForQuery: [], - startDate: '', - }; - - const MOCK_TOP_QUERIES = [ - { - doc_count: 5, - key: 'some-key', - }, - { - doc_count: 0, - key: 'another-key', - }, - ]; - const MOCK_RECENT_QUERIES = [ - { - document_ids: ['1', '2'], - query_string: 'some-query', - tags: ['some-tag'], - timestamp: 'some-timestamp', - }, - ]; - const MOCK_TOP_CLICKS = [ - { - key: 'highly-clicked-query', - doc_count: 1, - document: { - id: 'some-id', - engine: 'some-engine', - tags: [], - }, - clicks: { - doc_count: 100, - }, - }, - ]; - const MOCK_ANALYTICS_RESPONSE = { - allTags: ['some-tag'], - startDate: '1970-01-01', - recentQueries: MOCK_RECENT_QUERIES, - topQueries: MOCK_TOP_QUERIES, - topQueriesNoResults: MOCK_TOP_QUERIES, - topQueriesNoClicks: MOCK_TOP_QUERIES, - topQueriesWithClicks: MOCK_TOP_QUERIES, - totalClicks: 1000, - totalQueries: 5000, - totalQueriesNoResults: 500, - clicksPerDay: [0, 10, 50], - queriesPerDay: [10, 50, 100], - queriesNoResultsPerDay: [1, 2, 3], - }; - const MOCK_QUERY_RESPONSE = { - allTags: ['some-tag'], - startDate: '1970-01-01', - totalQueriesForQuery: 50, - queriesPerDayForQuery: [25, 0, 25], - topClicksForQuery: MOCK_TOP_CLICKS, - }; - - beforeEach(() => { - jest.clearAllMocks(); - history.location.search = ''; - }); - - it('has expected default values', () => { - mount(); - expect(AnalyticsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onAnalyticsDataLoad', () => { - it('should set state', () => { - mount(); - AnalyticsLogic.actions.onAnalyticsDataLoad(MOCK_ANALYTICS_RESPONSE); - - expect(AnalyticsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - ...MOCK_ANALYTICS_RESPONSE, - }); - }); - }); - - describe('onQueryDataLoad', () => { - it('should set state', () => { - mount(); - AnalyticsLogic.actions.onQueryDataLoad(MOCK_QUERY_RESPONSE); - - expect(AnalyticsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - ...MOCK_QUERY_RESPONSE, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadAnalyticsData', () => { - it('should set state', () => { - mount({ dataLoading: false }); - - AnalyticsLogic.actions.loadAnalyticsData(); - - expect(AnalyticsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and set state based on the response', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_ANALYTICS_RESPONSE)); - mount(); - jest.spyOn(AnalyticsLogic.actions, 'onAnalyticsDataLoad'); - - AnalyticsLogic.actions.loadAnalyticsData(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/analytics/queries', - { - query: { - start: DEFAULT_START_DATE, - end: DEFAULT_END_DATE, - size: 20, - }, - } - ); - expect(AnalyticsLogic.actions.onAnalyticsDataLoad).toHaveBeenCalledWith( - MOCK_ANALYTICS_RESPONSE - ); - }); - - it('parses and passes the current search query string', async () => { - (http.get as jest.Mock).mockReturnValueOnce({}); - history.location.search = '?start=1970-01-01&end=1970-01-02&&tag=some_tag'; - mount(); - - AnalyticsLogic.actions.loadAnalyticsData(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/analytics/queries', - { - query: { - start: '1970-01-01', - end: '1970-01-02', - tag: 'some_tag', - size: 20, - }, - } - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - AnalyticsLogic.actions.loadAnalyticsData(); - }); - }); - - describe('loadQueryData', () => { - it('should set state', () => { - mount({ dataLoading: false }); - - AnalyticsLogic.actions.loadQueryData('some-query'); - - expect(AnalyticsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and set state based on the response', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_QUERY_RESPONSE)); - mount(); - jest.spyOn(AnalyticsLogic.actions, 'onQueryDataLoad'); - - AnalyticsLogic.actions.loadQueryData('some-query'); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/analytics/queries/some-query', - { - query: { - start: DEFAULT_START_DATE, - end: DEFAULT_END_DATE, - }, - } - ); - expect(AnalyticsLogic.actions.onQueryDataLoad).toHaveBeenCalledWith(MOCK_QUERY_RESPONSE); - }); - - it('parses and passes the current search query string', async () => { - (http.get as jest.Mock).mockReturnValueOnce({}); - history.location.search = '?start=1970-12-30&end=1970-12-31&&tag=another_tag'; - mount(); - - AnalyticsLogic.actions.loadQueryData('some-query'); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/analytics/queries/some-query', - { - query: { - start: '1970-12-30', - end: '1970-12-31', - tag: 'another_tag', - }, - } - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - AnalyticsLogic.actions.loadQueryData('some-query'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts deleted file mode 100644 index c716d429f5514..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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 queryString from 'query-string'; - -import { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; -import { EngineLogic } from '../engine'; - -import { DEFAULT_START_DATE, DEFAULT_END_DATE } from './constants'; -import { AnalyticsData, QueryDetails } from './types'; - -interface AnalyticsValues extends AnalyticsData, QueryDetails { - dataLoading: boolean; -} - -interface AnalyticsActions { - onAnalyticsDataLoad(data: AnalyticsData): AnalyticsData; - onQueryDataLoad(data: QueryDetails): QueryDetails; - loadAnalyticsData(): void; - loadQueryData(query: string): string; -} - -export const AnalyticsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'analytics_logic'], - actions: () => ({ - onAnalyticsDataLoad: (data) => data, - onQueryDataLoad: (data) => data, - loadAnalyticsData: true, - loadQueryData: (query) => query, - }), - reducers: () => ({ - dataLoading: [ - true, - { - loadAnalyticsData: () => true, - loadQueryData: () => true, - onAnalyticsDataLoad: () => false, - onQueryDataLoad: () => false, - }, - ], - allTags: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { allTags }) => allTags, - // @ts-expect-error upgrade typescript v5.1.6 - onQueryDataLoad: (_, { allTags }) => allTags, - }, - ], - recentQueries: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { recentQueries }) => recentQueries, - }, - ], - topQueries: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { topQueries }) => topQueries, - }, - ], - topQueriesNoResults: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { topQueriesNoResults }) => topQueriesNoResults, - }, - ], - topQueriesNoClicks: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { topQueriesNoClicks }) => topQueriesNoClicks, - }, - ], - topQueriesWithClicks: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { topQueriesWithClicks }) => topQueriesWithClicks, - }, - ], - totalQueries: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { totalQueries }) => totalQueries, - }, - ], - totalQueriesNoResults: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { totalQueriesNoResults }) => totalQueriesNoResults, - }, - ], - totalClicks: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { totalClicks }) => totalClicks, - }, - ], - queriesPerDay: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { queriesPerDay }) => queriesPerDay, - }, - ], - queriesNoResultsPerDay: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { queriesNoResultsPerDay }) => queriesNoResultsPerDay, - }, - ], - clicksPerDay: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { clicksPerDay }) => clicksPerDay, - }, - ], - totalQueriesForQuery: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onQueryDataLoad: (_, { totalQueriesForQuery }) => totalQueriesForQuery, - }, - ], - queriesPerDayForQuery: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onQueryDataLoad: (_, { queriesPerDayForQuery }) => queriesPerDayForQuery, - }, - ], - topClicksForQuery: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onQueryDataLoad: (_, { topClicksForQuery }) => topClicksForQuery, - }, - ], - startDate: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - onAnalyticsDataLoad: (_, { startDate }) => startDate, - // @ts-expect-error upgrade typescript v5.1.6 - onQueryDataLoad: (_, { startDate }) => startDate, - }, - ], - }), - listeners: ({ actions }) => ({ - loadAnalyticsData: async () => { - const { history } = KibanaLogic.values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const { start, end, tag } = queryString.parse(history.location.search); - const query = { - start: start || DEFAULT_START_DATE, - end: end || DEFAULT_END_DATE, - tag, - size: 20, - }; - const url = `/internal/app_search/engines/${engineName}/analytics/queries`; - - const response = await http.get(url, { query }); - actions.onAnalyticsDataLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - loadQueryData: async (query) => { - const { history } = KibanaLogic.values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const { start, end, tag } = queryString.parse(history.location.search); - const queryParams = { - start: start || DEFAULT_START_DATE, - end: end || DEFAULT_END_DATE, - tag, - }; - const url = `/internal/app_search/engines/${engineName}/analytics/queries/${query}`; - - const response = await http.get(url, { query: queryParams }); - - actions.onQueryDataLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx deleted file mode 100644 index 3d0dd084c37af..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { AnalyticsRouter } from '.'; - -describe('AnalyticsRouter', () => { - // Detailed route testing is better done via E2E tests - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(Routes)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(9); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx deleted file mode 100644 index 4e93e6ed9112a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { Redirect } from 'react-router-dom'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { - ENGINE_ANALYTICS_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH, - ENGINE_ANALYTICS_RECENT_QUERIES_PATH, - ENGINE_ANALYTICS_QUERY_DETAILS_PATH, - ENGINE_ANALYTICS_QUERY_DETAIL_PATH, -} from '../../routes'; -import { generateEnginePath, getEngineBreadcrumbs } from '../engine'; -import { NotFound } from '../not_found'; - -import { ANALYTICS_TITLE } from './constants'; -import { - Analytics, - TopQueries, - TopQueriesNoResults, - TopQueriesNoClicks, - TopQueriesWithClicks, - RecentQueries, - QueryDetail, -} from './views'; - -export const AnalyticsRouter: React.FC = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx deleted file mode 100644 index c8158137fe602..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { EuiStat } from '@elastic/eui'; - -import { AnalyticsCards } from '.'; - -describe('AnalyticsCards', () => { - it('renders', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(EuiStat)).toHaveLength(2); - expect(wrapper.find('[data-test-subj="RedFish"]').prop('title')).toEqual(100); - expect(wrapper.find('[data-test-subj="RedFish"]').prop('description')).toEqual('Red fish'); - expect(wrapper.find('[data-test-subj="BlueFish"]').prop('title')).toEqual(2000); - expect(wrapper.find('[data-test-subj="BlueFish"]').prop('description')).toEqual('Blue fish'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx deleted file mode 100644 index ba71549455409..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui'; - -interface Props { - stats: Array<{ - text: string; - stat: number; - dataTestSubj?: string; - }>; -} -export const AnalyticsCards: React.FC = ({ stats }) => ( - - {stats.map(({ text, stat, dataTestSubj }) => ( - - - - - - ))} - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx deleted file mode 100644 index ded32c853be43..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 { mockKibanaValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { Chart, LineSeries, Axis, Tooltip } from '@elastic/charts'; - -import { AnalyticsChart } from '.'; - -describe('AnalyticsChart', () => { - const MOCK_DATA = [ - { x: '1970-01-01', y: 0 }, - { x: '1970-01-02', y: 1 }, - { x: '1970-01-03', y: 5 }, - { x: '1970-01-04', y: 50 }, - { x: '1970-01-05', y: 25 }, - ]; - - beforeAll(() => { - jest.clearAllMocks(); - }); - - it('renders an Elastic line chart', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(Chart).prop('size')).toEqual({ height: 300 }); - expect(wrapper.find(Axis)).toHaveLength(2); - expect(mockKibanaValues.charts.theme.useChartsBaseTheme).toHaveBeenCalled(); - - expect(wrapper.find(LineSeries)).toHaveLength(1); - expect(wrapper.find(LineSeries).prop('id')).toEqual('test'); - expect(wrapper.find(LineSeries).prop('data')).toEqual(MOCK_DATA); - }); - - it('renders multiple lines', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(LineSeries)).toHaveLength(3); - }); - - it('renders dashed lines', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(LineSeries).prop('lineSeriesStyle')?.line?.dash).toBeTruthy(); - }); - - it('formats x-axis dates correctly', () => { - const wrapper = shallow(); - const dateFormatter: Function = wrapper.find('#bottom-axis').prop('tickFormat'); - - expect(dateFormatter('1970-02-28')).toEqual('2/28'); - }); - - it('formats tooltip dates correctly', () => { - const wrapper = shallow(); - const dateFormatter = wrapper.find(Tooltip).prop('headerFormatter')!; - - expect(dateFormatter({ value: '1970-12-03', formattedValue: '1970-12-03' })).toEqual( - 'December 3, 1970' - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx deleted file mode 100644 index 2e23eb9a76f21..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import moment from 'moment'; - -import { Chart, Settings, LineSeries, CurveType, Axis, Tooltip } from '@elastic/charts'; - -import { i18n } from '@kbn/i18n'; - -import { KibanaLogic } from '../../../../shared/kibana'; - -import { X_AXIS_DATE_FORMAT, TOOLTIP_DATE_FORMAT } from '../constants'; - -interface ChartPoint { - x: string; // Date string - y: number; // # of clicks, queries, etc. -} -export type ChartData = ChartPoint[]; - -interface Props { - height?: number; - lines: Array<{ - id: string; - data: ChartData; - isDashed?: boolean; - }>; -} -export const AnalyticsChart: React.FC = ({ height = 300, lines }) => { - const { charts } = useValues(KibanaLogic); - - return ( - - moment(tooltip.value).format(TOOLTIP_DATE_FORMAT)} /> - - {lines.map(({ id, data, isDashed }) => ( - - ))} - moment(d).format(X_AXIS_DATE_FORMAT)} - gridLine={{ visible: true }} - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.test.tsx deleted file mode 100644 index 51da936216241..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.test.tsx +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 { setMockValues, mockKibanaValues } from '../../../../__mocks__/kea_logic'; - -import React, { ReactElement } from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; -import moment, { Moment } from 'moment'; - -import { EuiSelect, EuiDatePickerRange, EuiButton } from '@elastic/eui'; - -import { DEFAULT_START_DATE, DEFAULT_END_DATE } from '../constants'; - -import { AnalyticsFilters } from '.'; - -describe('AnalyticsFilters', () => { - const { history } = mockKibanaValues; - - const values = { - allTags: ['All Analytics Tags'], // Comes from the server API - history, - }; - - const newStartDateMoment = moment('1970-01-30'); - const newEndDateMoment = moment('1970-01-31'); - - let wrapper: ShallowWrapper; - const getTagsSelect = () => wrapper.find(EuiSelect); - const getDateRangePicker = () => wrapper.find(EuiDatePickerRange); - const getStartDatePicker = () => getDateRangePicker().prop('startDateControl') as ReactElement; - const getEndDatePicker = () => getDateRangePicker().prop('endDateControl') as ReactElement; - const getApplyButton = () => wrapper.find(EuiButton); - - beforeEach(() => { - jest.clearAllMocks(); - history.location.search = ''; - setMockValues(values); - }); - - it('renders', () => { - wrapper = shallow(); - - expect(wrapper.find(EuiSelect)).toHaveLength(1); - expect(wrapper.find(EuiDatePickerRange)).toHaveLength(1); - }); - - it('renders tags & dates with default values when no search query params are present', () => { - wrapper = shallow(); - - expect(getTagsSelect().prop('value')).toEqual(''); - expect(getStartDatePicker().props.startDate._i).toEqual(DEFAULT_START_DATE); - expect(getEndDatePicker().props.endDate._i).toEqual(DEFAULT_END_DATE); - }); - - describe('tags select', () => { - beforeEach(() => { - history.location.search = '?tag=tag1'; - const allTags = [...values.allTags, 'tag1', 'tag2', 'tag3']; - setMockValues({ ...values, allTags }); - - wrapper = shallow(); - }); - - it('renders the tags select with currentTag value and allTags options', () => { - const tagsSelect = getTagsSelect(); - - expect(tagsSelect.prop('value')).toEqual('tag1'); - expect(tagsSelect.prop('options')).toEqual([ - { value: '', text: 'All analytics tags' }, - { value: 'tag1', text: 'tag1' }, - { value: 'tag2', text: 'tag2' }, - { value: 'tag3', text: 'tag3' }, - ]); - }); - - it('updates currentTag on new tag select', () => { - getTagsSelect().simulate('change', { target: { value: 'tag3' } }); - - expect(getTagsSelect().prop('value')).toEqual('tag3'); - }); - }); - - describe('date pickers', () => { - beforeEach(() => { - history.location.search = '?start=1970-01-01&end=1970-01-02'; - - wrapper = shallow(); - }); - - it('renders the start date picker', () => { - const startDatePicker = getStartDatePicker(); - expect(startDatePicker.props.selected._i).toEqual('1970-01-01'); - expect(startDatePicker.props.startDate._i).toEqual('1970-01-01'); - }); - - it('renders the end date picker', () => { - const endDatePicker = getEndDatePicker(); - expect(endDatePicker.props.selected._i).toEqual('1970-01-02'); - expect(endDatePicker.props.endDate._i).toEqual('1970-01-02'); - }); - - it('updates startDate on start date pick', () => { - getStartDatePicker().props.onChange(newStartDateMoment); - - expect(getStartDatePicker().props.startDate._i).toEqual('1970-01-30'); - }); - - it('updates endDate on start date pick', () => { - getEndDatePicker().props.onChange(newEndDateMoment); - - expect(getEndDatePicker().props.endDate._i).toEqual('1970-01-31'); - }); - }); - - describe('invalid date ranges', () => { - beforeEach(() => { - history.location.search = '?start=1970-01-02&end=1970-01-01'; - - wrapper = shallow(); - }); - - it('renders the date pickers as invalid', () => { - expect(getStartDatePicker().props.isInvalid).toEqual(true); - expect(getEndDatePicker().props.isInvalid).toEqual(true); - }); - - it('disables the apply button', () => { - expect(getApplyButton().prop('isDisabled')).toEqual(true); - }); - }); - - describe('applying filters', () => { - const updateState = ({ start, end, tag }: { start: Moment; end: Moment; tag: string }) => { - getTagsSelect().simulate('change', { target: { value: tag } }); - getStartDatePicker().props.onChange(start); - getEndDatePicker().props.onChange(end); - }; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('pushes up new tag & date state to the search query', () => { - updateState({ start: newStartDateMoment, end: newEndDateMoment, tag: 'tag2' }); - getApplyButton().simulate('click'); - - expect(history.push).toHaveBeenCalledWith({ - search: 'end=1970-01-31&start=1970-01-30&tag=tag2', - }); - }); - - it('does not push up the tag param if empty (set to all tags)', () => { - updateState({ start: newStartDateMoment, end: newEndDateMoment, tag: '' }); - getApplyButton().simulate('click'); - - expect(history.push).toHaveBeenCalledWith({ - search: 'end=1970-01-31&start=1970-01-30', - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx deleted file mode 100644 index e1d985ad92b48..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useValues } from 'kea'; -import moment from 'moment'; -import queryString from 'query-string'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiSelect, - EuiDatePickerRange, - EuiDatePicker, - EuiButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AnalyticsLogic } from '..'; -import { KibanaLogic } from '../../../../shared/kibana'; - -import { DEFAULT_START_DATE, DEFAULT_END_DATE, SERVER_DATE_FORMAT } from '../constants'; -import { convertTagsToSelectOptions } from '../utils'; - -export const AnalyticsFilters: React.FC = () => { - const { allTags } = useValues(AnalyticsLogic); - const { history } = useValues(KibanaLogic); - - // Parse out existing filters from URL query string - const { start, end, tag } = queryString.parse(history.location.search); - const [startDate, setStartDate] = useState( - start ? moment(start, SERVER_DATE_FORMAT) : moment(DEFAULT_START_DATE) - ); - const [endDate, setEndDate] = useState( - end ? moment(end, SERVER_DATE_FORMAT) : moment(DEFAULT_END_DATE) - ); - const [currentTag, setCurrentTag] = useState((tag as string) || ''); - - // Set the current URL query string on filter - const onApplyFilters = () => { - const search = queryString.stringify({ - start: moment(startDate).format(SERVER_DATE_FORMAT), - end: moment(endDate).format(SERVER_DATE_FORMAT), - tag: currentTag || undefined, - }); - history.push({ search }); - }; - - const hasInvalidDateRange = startDate > endDate; - - return ( - - - setCurrentTag(e.target.value)} - aria-label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.tagAriaLabel', - { defaultMessage: 'Filter by analytics tag"' } - )} - fullWidth - /> - - - date && setStartDate(date)} - startDate={startDate} - endDate={endDate} - isInvalid={hasInvalidDateRange} - aria-label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel', - { defaultMessage: 'Filter by start date' } - )} - locale={i18n.getLocale()} - /> - } - endDateControl={ - date && setEndDate(date)} - startDate={startDate} - endDate={endDate} - isInvalid={hasInvalidDateRange} - aria-label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel', - { defaultMessage: 'Filter by end date' } - )} - locale={i18n.getLocale()} - /> - } - fullWidth - /> - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.applyButtonLabel', - { defaultMessage: 'Apply filters' } - )} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx deleted file mode 100644 index 4fbd70069f9d3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { mockKibanaValues } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFieldSearch } from '@elastic/eui'; - -import { AnalyticsSearch } from '.'; - -describe('AnalyticsSearch', () => { - const { navigateToUrl } = mockKibanaValues; - const preventDefault = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const wrapper = shallow(); - const setSearchValue = (value: string) => - wrapper.find(EuiFieldSearch).simulate('change', { target: { value } }); - - it('renders', () => { - expect(wrapper.find(EuiFieldSearch)).toHaveLength(1); - }); - - it('updates searchValue state on input change', () => { - expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual(''); - - setSearchValue('some-query'); - expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('some-query'); - }); - - it('sends the user to the query detail page on search', () => { - wrapper.find('form').simulate('submit', { preventDefault }); - - expect(preventDefault).toHaveBeenCalled(); - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/analytics/query_detail/some-query' - ); - }); - - it('falls back to showing the "" query if searchValue is empty', () => { - setSearchValue(''); - wrapper.find('form').simulate('submit', { preventDefault }); - - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/analytics/query_detail/%22%22' // "" gets encoded - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx deleted file mode 100644 index 4f2b525aaa168..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiButton, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { KibanaLogic } from '../../../../shared/kibana'; -import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH } from '../../../routes'; -import { generateEnginePath } from '../../engine'; - -export const AnalyticsSearch: React.FC = () => { - const [searchValue, setSearchValue] = useState(''); - - const { navigateToUrl } = useValues(KibanaLogic); - const viewQueryDetails = (e: React.SyntheticEvent) => { - e.preventDefault(); - const query = searchValue || '""'; - navigateToUrl(generateEnginePath(ENGINE_ANALYTICS_QUERY_DETAIL_PATH, { query })); - }; - - return ( -
- - - setSearchValue(e.target.value)} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchPlaceholder', - { defaultMessage: 'Go to search term' } - )} - fullWidth - /> - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetailSearchButtonLabel', - { defaultMessage: 'View details' } - )} - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx deleted file mode 100644 index f4272a7ea1337..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { EuiIcon } from '@elastic/eui'; - -import { AnalyticsSection } from '.'; - -describe('AnalyticsSection', () => { - it('renders', () => { - const wrapper = shallow( - -
Test
-
- ); - - expect(wrapper.find('h2').text()).toEqual('Lorem ipsum'); - expect(wrapper.find('p').text()).toEqual('Dolor sit amet.'); - expect(wrapper.find('[data-test-subj="HelloWorld"]')).toHaveLength(1); - }); - - it('renders an optional icon', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(EuiIcon).prop('type')).toEqual('eye'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.tsx deleted file mode 100644 index 68ab40e9b17b8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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, { FC, PropsWithChildren } from 'react'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiPageSection, - EuiSpacer, - EuiText, - EuiTitle, - IconType, -} from '@elastic/eui'; - -interface Props { - iconType?: IconType; - subtitle: string; - title: string; -} -export const AnalyticsSection: FC> = ({ - title, - subtitle, - iconType, - children, -}) => ( -
-
- - {iconType && ( - - - - )} - - -

{title}

-
-
-
- - -

{subtitle}

-
-
- - {children} -
-); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx deleted file mode 100644 index 7ed8f59e379e5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/react_router'; -import '../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { runActionColumnTests } from './test_helpers/shared_columns_tests'; - -import { AnalyticsTable } from '.'; - -describe('AnalyticsTable', () => { - const items = [ - { - key: 'some search', - tags: ['tagA'], - searches: { doc_count: 100 }, - clicks: { doc_count: 10 }, - }, - { - key: 'another search', - tags: ['tagB'], - searches: { doc_count: 99 }, - clicks: { doc_count: 9 }, - }, - { - key: '', - tags: ['tagA', 'tagB'], - searches: { doc_count: 1 }, - clicks: { doc_count: 0 }, - }, - ]; - - it('renders', () => { - const wrapper = mountWithIntl(); - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Search term'); - expect(tableContent).toContain('some search'); - expect(tableContent).toContain('another search'); - expect(tableContent).toContain('""'); - - expect(tableContent).toContain('Analytics tags'); - expect(tableContent).toContain('tagA'); - expect(tableContent).toContain('tagB'); - expect(wrapper.find(EuiBadge)).toHaveLength(4); - - expect(tableContent).toContain('Queries'); - expect(tableContent).toContain('100'); - expect(tableContent).toContain('99'); - expect(tableContent).toContain('1'); - expect(tableContent).not.toContain('Clicks'); - }); - - it('renders a clicks column if hasClicks is passed', () => { - const wrapper = mountWithIntl(); - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Clicks'); - expect(tableContent).toContain('10'); - expect(tableContent).toContain('9'); - expect(tableContent).toContain('0'); - }); - - it('renders tag counts instead of tag names if isSmall is passed', () => { - const wrapper = mountWithIntl(); - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Analytics tags'); - expect(tableContent).toContain('1 tag'); - expect(tableContent).toContain('2 tags'); - expect(wrapper.find(EuiBadge)).toHaveLength(3); - }); - - describe('renders an action column', () => { - const wrapper = mountWithIntl(); - runActionColumnTests(wrapper); - }); - - it('renders an empty prompt if no items are passed', () => { - const wrapper = mountWithIntl(); - const promptContent = wrapper.find(EuiEmptyPrompt).text(); - - expect(promptContent).toContain('No queries were performed during this time period.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx deleted file mode 100644 index a5e981e7f7b8d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { Query } from '../../types'; - -import { - TERM_COLUMN_PROPS, - TAGS_LIST_COLUMN, - TAGS_COUNT_COLUMN, - COUNT_COLUMN_PROPS, - ACTIONS_COLUMN, -} from './shared_columns'; - -interface Props { - items: Query[]; - hasClicks?: boolean; - isSmall?: boolean; -} -type Columns = Array>; - -export const AnalyticsTable: React.FC = ({ items, hasClicks, isSmall }) => { - const TERM_COLUMN = { - field: 'key', - ...TERM_COLUMN_PROPS, - }; - - const TAGS_COLUMN = isSmall ? TAGS_COUNT_COLUMN : TAGS_LIST_COLUMN; - - const COUNT_COLUMNS = [ - { - field: 'searches.doc_count', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.queriesColumn', - { defaultMessage: 'Queries' } - ), - ...COUNT_COLUMN_PROPS, - }, - ]; - if (hasClicks) { - COUNT_COLUMNS.push({ - field: 'clicks.doc_count', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.clicksColumn', { - defaultMessage: 'Clicks', - }), - ...COUNT_COLUMN_PROPS, - }); - } - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesTitle', - { defaultMessage: 'No queries to display' } - )} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noQueriesDescription', - { defaultMessage: 'No queries were performed during this time period.' } - )} - /> - } - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/index.ts deleted file mode 100644 index 6dff4c4973b1e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { AnalyticsTable } from './analytics_table'; -export { RecentQueriesTable } from './recent_queries_table'; -export { QueryClicksTable } from './query_clicks_table'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx deleted file mode 100644 index 2fae41b1872bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/react_router'; -import '../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { EuiBasicTable, EuiLink, EuiBadge, EuiEmptyPrompt } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { QueryClicksTable } from '.'; - -describe('QueryClicksTable', () => { - const items = [ - { - key: 'some-document', - document: { - id: 'some-document', - engine: 'some-engine', - }, - tags: ['tagA'], - doc_count: 10, - }, - { - key: 'another-document', - document: { - id: 'another-document', - engine: 'another-engine', - }, - tags: ['tagB'], - doc_count: 5, - }, - { - key: 'deleted-document', - tags: [], - doc_count: 1, - }, - ]; - - it('renders', () => { - const wrapper = mountWithIntl(); - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Documents'); - expect(tableContent).toContain('some-document'); - expect(tableContent).toContain('another-document'); - expect(tableContent).toContain('deleted-document'); - - expect(wrapper.find(EuiLink).first().prop('href')).toEqual( - '/app/enterprise_search/engines/some-engine/documents/some-document' - ); - expect(wrapper.find(EuiLink).last().prop('href')).toEqual( - '/app/enterprise_search/engines/another-engine/documents/another-document' - ); - // deleted-document should not have a link - - expect(tableContent).toContain('Analytics tags'); - expect(tableContent).toContain('tagA'); - expect(tableContent).toContain('tagB'); - expect(wrapper.find(EuiBadge)).toHaveLength(2); - - expect(tableContent).toContain('Clicks'); - expect(tableContent).toContain('10'); - expect(tableContent).toContain('5'); - expect(tableContent).toContain('1'); - }); - - it('renders an empty prompt if no items are passed', () => { - const wrapper = mountWithIntl(); - const promptContent = wrapper.find(EuiEmptyPrompt).text(); - - expect(promptContent).toContain('No clicks'); - expect(promptContent).toContain('No documents have been clicked from this query.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx deleted file mode 100644 index 5ca39bd43c21a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; -import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../../../routes'; -import { DOCUMENTS_TITLE } from '../../../documents'; -import { generateEnginePath } from '../../../engine'; - -import { QueryClick } from '../../types'; - -import { FIRST_COLUMN_PROPS, TAGS_LIST_COLUMN, COUNT_COLUMN_PROPS } from './shared_columns'; - -interface Props { - items: QueryClick[]; -} -type Columns = Array>; - -export const QueryClicksTable: React.FC = ({ items }) => { - const DOCUMENT_COLUMN = { - ...FIRST_COLUMN_PROPS, - field: 'document', - name: DOCUMENTS_TITLE, - render: (document: QueryClick['document'], query: QueryClick) => { - return document ? ( - - {document.id} - - ) : ( - query.key - ); - }, - }; - - const CLICKS_COLUMN = { - ...COUNT_COLUMN_PROPS, - field: 'doc_count', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.clicksColumn', { - defaultMessage: 'Clicks', - }), - }; - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksTitle', - { defaultMessage: 'No clicks' } - )} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noClicksDescription', - { defaultMessage: 'No documents have been clicked from this query.' } - )} - /> - } - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx deleted file mode 100644 index c3cfdf33f9d8a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/react_router'; -import '../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { runActionColumnTests } from './test_helpers/shared_columns_tests'; - -import { RecentQueriesTable } from '.'; - -describe('RecentQueriesTable', () => { - const items = [ - { - query_string: 'some search', - timestamp: '1970-01-03T12:00:00Z', - tags: ['tagA'], - document_ids: ['documentA', 'documentB'], - }, - { - query_string: 'another search', - timestamp: '1970-01-02T12:00:00Z', - tags: ['tagB'], - document_ids: ['documentC'], - }, - { - query_string: '', - timestamp: '1970-01-01T12:00:00Z', - tags: ['tagA', 'tagB'], - document_ids: ['documentA', 'documentB', 'documentC'], - }, - ]; - - it('renders', () => { - const wrapper = mountWithIntl(); - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Search term'); - expect(tableContent).toContain('some search'); - expect(tableContent).toContain('another search'); - expect(tableContent).toContain('""'); - - expect(tableContent).toContain('Time'); - expect(tableContent).toContain('Jan 3, 1970'); - expect(tableContent).toContain('Jan 2, 1970'); - expect(tableContent).toContain('Jan 1, 1970'); - - expect(tableContent).toContain('Analytics tags'); - expect(tableContent).toContain('tagA'); - expect(tableContent).toContain('tagB'); - expect(wrapper.find(EuiBadge)).toHaveLength(4); - - expect(tableContent).toContain('Results'); - expect(tableContent).toContain('2'); - expect(tableContent).toContain('1'); - expect(tableContent).toContain('3'); - }); - - describe('renders an action column', () => { - const wrapper = mountWithIntl(); - runActionColumnTests(wrapper); - }); - - it('renders an empty prompt if no items are passed', () => { - const wrapper = mountWithIntl(); - const promptContent = wrapper.find(EuiEmptyPrompt).text(); - - expect(promptContent).toContain('No recent queries'); - expect(promptContent).toContain('Queries will appear here as they are received.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx deleted file mode 100644 index 92511c9a2233c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { FormattedDateTime } from '../../../../utils/formatted_date_time'; -import { RecentQuery } from '../../types'; - -import { - TERM_COLUMN_PROPS, - TAGS_LIST_COLUMN, - COUNT_COLUMN_PROPS, - ACTIONS_COLUMN, -} from './shared_columns'; - -interface Props { - items: RecentQuery[]; -} -type Columns = Array>; - -export const RecentQueriesTable: React.FC = ({ items }) => { - const TERM_COLUMN = { - ...TERM_COLUMN_PROPS, - field: 'query_string', - }; - - const TIME_COLUMN = { - field: 'timestamp', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.timeColumn', { - defaultMessage: 'Time', - }), - render: (timestamp: RecentQuery['timestamp']) => ( - - ), - width: '200px', - }; - - const RESULTS_COLUMN = { - ...COUNT_COLUMN_PROPS, - field: 'document_ids', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.resultsColumn', { - defaultMessage: 'Results', - }), - render: (documents: RecentQuery['document_ids']) => documents.length, - }; - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesTitle', - { defaultMessage: 'No recent queries' } - )} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.empty.noRecentQueriesDescription', - { defaultMessage: 'Queries will appear here as they are received.' } - )} - /> - } - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx deleted file mode 100644 index b4ddf18b8adb0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 { EDIT_BUTTON_LABEL } from '../../../../../shared/constants'; -import { flashAPIErrors } from '../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../shared/http'; -import { KibanaLogic } from '../../../../../shared/kibana'; -import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; -import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH, ENGINE_CURATION_PATH } from '../../../../routes'; -import { generateEnginePath, EngineLogic } from '../../../engine'; -import { Query, RecentQuery } from '../../types'; - -import { TagsList, TagsCount } from './tags'; - -/** - * Shared columns / column properties between separate analytics tables - */ - -export const FIRST_COLUMN_PROPS = { - truncateText: true, - width: '25%', - mobileOptions: { - enlarge: true, - width: '100%', - }, -}; - -export const TERM_COLUMN_PROPS = { - // Field key changes per-table - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.termColumn', { - defaultMessage: 'Search term', - }), - render: (query: Query['key']) => { - if (!query) query = '""'; - return ( - - {query} - - ); - }, - ...FIRST_COLUMN_PROPS, -}; - -export const ACTIONS_COLUMN = { - width: '90px', - actions: [ - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAction', { - defaultMessage: 'View', - }), - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.viewTooltip', - { defaultMessage: 'View query analytics' } - ), - type: 'icon', - icon: 'eye', - color: 'primary', - onClick: (item: Query | RecentQuery) => { - const { navigateToUrl } = KibanaLogic.values; - - const query = (item as Query).key || (item as RecentQuery).query_string || '""'; - navigateToUrl(generateEnginePath(ENGINE_ANALYTICS_QUERY_DETAIL_PATH, { query })); - }, - 'data-test-subj': 'AnalyticsTableViewQueryButton', - }, - { - name: EDIT_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.editTooltip', - { defaultMessage: 'Manage curation' } - ), - type: 'icon', - icon: 'package', - onClick: async (item: Query | RecentQuery) => { - const { http } = HttpLogic.values; - const { navigateToUrl } = KibanaLogic.values; - const { engineName } = EngineLogic.values; - - try { - const query = (item as Query).key || (item as RecentQuery).query_string || '""'; - const response = await http.post<{ id: string }>( - `/internal/app_search/engines/${engineName}/curations/find_or_create`, - { body: JSON.stringify({ query }) } - ); - navigateToUrl(generateEnginePath(ENGINE_CURATION_PATH, { curationId: response.id })); - } catch (e) { - flashAPIErrors(e); - } - }, - 'data-test-subj': 'AnalyticsTableEditQueryButton', - }, - ], -}; - -export const TAGS_COLUMN_PROPS = { - field: 'tags', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsColumn', { - defaultMessage: 'Analytics tags', - }), - truncateText: true, -}; -export const TAGS_LIST_COLUMN = { - ...TAGS_COLUMN_PROPS, - render: (tags: Query['tags']) => , -}; -export const TAGS_COUNT_COLUMN = { - ...TAGS_COLUMN_PROPS, - render: (tags: Query['tags']) => , -}; - -export const COUNT_COLUMN_PROPS = { - dataType: 'number', - width: '100px', -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.scss deleted file mode 100644 index 4de1c5e6226fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.scss +++ /dev/null @@ -1,3 +0,0 @@ -.tagsList .euiBadge { - max-width: $euiSize * 9; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.test.tsx deleted file mode 100644 index 9a42da7461e0c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { EuiBadge, EuiToolTip } from '@elastic/eui'; - -import { TagsList, TagsCount } from './tags'; - -describe('TagsList', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiBadge)).toHaveLength(1); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('test'); - }); - - it('renders >2 badges in a tooltip list', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiBadge)).toHaveLength(3); - expect(wrapper.find(EuiToolTip)).toHaveLength(1); - - expect(wrapper.find(EuiBadge).at(0).prop('children')).toEqual('1'); - expect(wrapper.find(EuiBadge).at(1).prop('children')).toEqual('2'); - expect(wrapper.find(EuiBadge).at(2).prop('children')).toEqual('and 3 more'); - expect(wrapper.find(EuiToolTip).prop('content')).toEqual('3, 4, 5'); - }); - - it('does not render if missing tags', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); - -describe('TagsCount', () => { - it('renders a count and all tags in a tooltip', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiToolTip)).toHaveLength(1); - expect(wrapper.find(EuiBadge)).toHaveLength(1); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('3 tags'); - expect(wrapper.find(EuiToolTip).prop('content')).toEqual('1, 2, 3'); - }); - - it('handles pluralization correctly', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiToolTip)).toHaveLength(1); - expect(wrapper.find(EuiBadge)).toHaveLength(1); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('1 tag'); - expect(wrapper.find(EuiToolTip).prop('content')).toEqual('1'); - }); - - it('does not render if missing tags', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.tsx deleted file mode 100644 index 241abfabfab4c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/tags.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 { EuiBadgeGroup, EuiBadge, EuiToolTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { Query } from '../../types'; - -import './tags.scss'; - -interface Props { - tags?: Query['tags']; -} - -export const TagsCount: React.FC = ({ tags }) => { - if (!tags?.length) return null; - - return ( - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.tagsCountBadge', { - defaultMessage: '{tagsCount, plural, one {# tag} other {# tags}}', - values: { tagsCount: tags.length }, - })} - - - ); -}; - -export const TagsList: React.FC = ({ tags }) => { - if (!tags?.length) return null; - - const displayedTags = tags.slice(0, 2); - const tooltipTags = tags.slice(2); - - return ( - - {displayedTags.map((tag: string) => ( - - {tag} - - ))} - {tooltipTags.length > 0 && ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.table.moreTagsBadge', - { - defaultMessage: 'and {moreTagsCount} more', - values: { moreTagsCount: tooltipTags.length }, - } - )} - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx deleted file mode 100644 index 3033c1dcbeea0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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, - mockKibanaValues, - mockFlashMessageHelpers, -} from '../../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/engine_logic.mock'; - -import { ReactWrapper } from 'enzyme'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -export const runActionColumnTests = (wrapper: ReactWrapper) => { - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { flashAPIErrors } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('view action', () => { - it('navigates to the query detail view', () => { - wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first().simulate('click'); - - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/analytics/query_detail/some%20search' - ); - }); - - it('falls back to "" for the empty query', () => { - wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').last().simulate('click'); - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/analytics/query_detail/%22%22' - ); - }); - }); - - describe('edit action', () => { - it('calls the find_or_create curation API, then navigates the user to the curation', async () => { - http.post.mockReturnValue(Promise.resolve({ id: 'cur-123456789' })); - wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first().simulate('click'); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations/find_or_create', - { - body: JSON.stringify({ query: 'some search' }), - } - ); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-123456789'); - }); - - it('falls back to "" for the empty query', async () => { - http.post.mockReturnValue(Promise.resolve({ id: 'cur-987654321' })); - wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').last().simulate('click'); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations/find_or_create', - { - body: JSON.stringify({ query: '""' }), - } - ); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-987654321'); - }); - - it('handles API errors', async () => { - http.post.mockReturnValue(Promise.reject()); - wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first().simulate('click'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalled(); - }); - }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/index.ts deleted file mode 100644 index 5309681b80d6d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 { AnalyticsCards } from './analytics_cards'; -export { AnalyticsChart } from './analytics_chart'; -export { AnalyticsFilters } from './analytics_filters'; -export { AnalyticsSection } from './analytics_section'; -export { AnalyticsSearch } from './analytics_search'; -export { AnalyticsTable, RecentQueriesTable, QueryClicksTable } from './analytics_tables'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts deleted file mode 100644 index 75001f5bc86d6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 moment from 'moment'; - -import { i18n } from '@kbn/i18n'; - -export const ANALYTICS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.title', - { defaultMessage: 'Analytics' } -); - -// Total card titles -export const TOTAL_DOCUMENTS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments', - { defaultMessage: 'Total documents' } -); -export const TOTAL_API_OPERATIONS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations', - { defaultMessage: 'Total API operations' } -); -export const TOTAL_QUERIES = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries', - { defaultMessage: 'Total queries' } -); -export const TOTAL_QUERIES_NO_RESULTS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueriesNoResults', - { defaultMessage: 'Total queries with no results' } -); -export const TOTAL_CLICKS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks', - { defaultMessage: 'Total clicks' } -); - -// Queries sub-pages -export const TOP_QUERIES = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesTitle', - { defaultMessage: 'Top queries' } -); -export const TOP_QUERIES_NO_RESULTS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoResultsTitle', - { defaultMessage: 'Top queries with no results' } -); -export const TOP_QUERIES_NO_CLICKS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesNoClicksTitle', - { defaultMessage: 'Top queries with no clicks' } -); -export const TOP_QUERIES_WITH_CLICKS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.topQueriesWithClicksTitle', - { defaultMessage: 'Top queries with clicks' } -); -export const RECENT_QUERIES = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesTitle', - { defaultMessage: 'Recent queries' } -); - -// Date formats & dates -export const SERVER_DATE_FORMAT = 'YYYY-MM-DD'; -export const TOOLTIP_DATE_FORMAT = 'MMMM D, YYYY'; -export const X_AXIS_DATE_FORMAT = 'M/D'; - -export const DEFAULT_START_DATE = moment().subtract(6, 'days').format(SERVER_DATE_FORMAT); -export const DEFAULT_END_DATE = moment().format(SERVER_DATE_FORMAT); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/index.ts deleted file mode 100644 index d812f61747b1d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { ANALYTICS_TITLE } from './constants'; -export { AnalyticsLogic } from './analytics_logic'; -export { AnalyticsRouter } from './analytics_router'; -export { AnalyticsCards, AnalyticsChart } from './components'; -export { convertToChartData } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/types.ts deleted file mode 100644 index 5bc7e94ea362e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 interface Query { - key: string; - tags?: string[]; - searches?: { doc_count: number }; - clicks?: { doc_count: number }; -} - -export interface QueryClick extends Query { - document?: { - id: string; - engine: string; - }; -} - -export interface RecentQuery { - query_string: string; - timestamp: string; - tags: string[]; - document_ids: string[]; -} - -/** - * API response data - */ - -interface BaseData { - allTags: string[]; - startDate: string; - // NOTE: The API sends us back even more data than this (e.g., - // analyticsUnavailable, endDate, currentTag, logRetentionSettings, query), - // but we currently don't need that data in our front-end code, - // so I'm opting not to list them in our types -} - -export interface AnalyticsData extends BaseData { - recentQueries: RecentQuery[]; - topQueries: Query[]; - topQueriesWithClicks: Query[]; - topQueriesNoClicks: Query[]; - topQueriesNoResults: Query[]; - totalClicks: number; - totalQueries: number; - totalQueriesNoResults: number; - clicksPerDay: number[]; - queriesPerDay: number[]; - queriesNoResultsPerDay: number[]; -} - -export interface QueryDetails extends BaseData { - totalQueriesForQuery: number; - queriesPerDayForQuery: number[]; - topClicksForQuery: QueryClick[]; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.test.ts deleted file mode 100644 index 2bf78a53a9a10..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { convertToChartData, convertTagsToSelectOptions } from './utils'; - -describe('convertToChartData', () => { - it('converts server-side analytics data into an array of objects that Elastic Charts can consume', () => { - expect( - convertToChartData({ - startDate: '1970-01-01', - data: [0, 1, 5, 50, 25], - }) - ).toEqual([ - { x: '1970-01-01', y: 0 }, - { x: '1970-01-02', y: 1 }, - { x: '1970-01-03', y: 5 }, - { x: '1970-01-04', y: 50 }, - { x: '1970-01-05', y: 25 }, - ]); - }); -}); - -describe('convertTagsToSelectOptions', () => { - it('converts server-side tag data into an array of objects that EuiSelect can consume', () => { - expect( - convertTagsToSelectOptions([ - 'All Analytics Tags', - 'lorem_ipsum', - 'dolor_sit', - 'amet', - 'consectetur_adipiscing_elit', - ]) - ).toEqual([ - { value: '', text: 'All analytics tags' }, - { value: 'lorem_ipsum', text: 'lorem_ipsum' }, - { value: 'dolor_sit', text: 'dolor_sit' }, - { value: 'amet', text: 'amet' }, - { value: 'consectetur_adipiscing_elit', text: 'consectetur_adipiscing_elit' }, - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts deleted file mode 100644 index db679b0f387e8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 moment from 'moment'; - -import { EuiSelectProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ChartData } from './components/analytics_chart'; -import { SERVER_DATE_FORMAT } from './constants'; - -interface ConvertToChartData { - data: number[]; - startDate: string; -} -export const convertToChartData = ({ data, startDate }: ConvertToChartData): ChartData => { - const date = moment(startDate, SERVER_DATE_FORMAT); - return data.map((y, index) => ({ - x: moment(date).add(index, 'days').format(SERVER_DATE_FORMAT), - y, - })); -}; - -export const convertTagsToSelectOptions = (tags: string[]): EuiSelectProps['options'] => { - // Our server API returns an initial default tag for us, but we don't want to use it because - // it's not i18n'ed, and also setting the value to '' is nicer for select/param UX - tags = tags.slice(1); - - const DEFAULT_OPTION = { - value: '', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.allTagsDropDownOptionLabel', - { defaultMessage: 'All analytics tags' } - ), - }; - - return [ - DEFAULT_OPTION, - ...tags.map((tag: string) => ({ - value: tag, - text: tag, - })), - ]; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.scss deleted file mode 100644 index 9b24a64390d4f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.scss +++ /dev/null @@ -1,5 +0,0 @@ -.analyticsOverviewTables { - @include euiBreakpoint('xs', 's', 'm', 'l') { - flex-direction: column; // Force full width on table panels earlier to ensure content stays legible - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx deleted file mode 100644 index ea01ad974b9a1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SuggestedCurationsCallout } from '../../engine_overview/components/suggested_curations_callout'; -import { - AnalyticsCards, - AnalyticsChart, - AnalyticsSection, - AnalyticsTable, - RecentQueriesTable, -} from '../components'; - -import { Analytics, ViewAllButton } from './analytics'; - -describe('Analytics overview', () => { - it('renders', () => { - setMockValues({ - totalQueries: 3, - totalQueriesNoResults: 2, - totalClicks: 1, - queriesPerDay: [10, 20, 30], - queriesNoResultsPerDay: [1, 2, 3], - clicksPerDay: [0, 1, 5], - startDate: '1970-01-01', - topQueries: [], - topQueriesNoResults: [], - topQueriesNoClicks: [], - topQueriesWithClicks: [], - recentQueries: [], - }); - const wrapper = shallow(); - - expect(wrapper.find(SuggestedCurationsCallout)).toHaveLength(1); - expect(wrapper.find(AnalyticsCards)).toHaveLength(1); - expect(wrapper.find(AnalyticsChart)).toHaveLength(1); - expect(wrapper.find(AnalyticsSection)).toHaveLength(2); - expect(wrapper.find(AnalyticsTable)).toHaveLength(4); - expect(wrapper.find(RecentQueriesTable)).toHaveLength(1); - }); - - describe('ViewAllButton', () => { - it('renders', () => { - const to = '/analytics/top_queries'; - const wrapper = shallow(); - - expect(wrapper.prop('to')).toEqual(to); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx deleted file mode 100644 index dd88d6ed28e35..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '..'; -import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; -import { CursorIcon } from '../../../icons'; - -import { - ENGINE_ANALYTICS_TOP_QUERIES_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH, - ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH, - ENGINE_ANALYTICS_RECENT_QUERIES_PATH, -} from '../../../routes'; -import { DataPanel } from '../../data_panel'; -import { generateEnginePath } from '../../engine'; - -import { SuggestedCurationsCallout } from '../../engine_overview/components/suggested_curations_callout'; -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSection, AnalyticsTable, RecentQueriesTable } from '../components'; -import { - ANALYTICS_TITLE, - TOTAL_QUERIES, - TOTAL_QUERIES_NO_RESULTS, - TOTAL_CLICKS, - TOP_QUERIES, - TOP_QUERIES_NO_RESULTS, - TOP_QUERIES_WITH_CLICKS, - TOP_QUERIES_NO_CLICKS, - RECENT_QUERIES, -} from '../constants'; - -import './analytics.scss'; - -export const Analytics: React.FC = () => { - const { - totalQueries, - totalQueriesNoResults, - totalClicks, - queriesPerDay, - queriesNoResultsPerDay, - clicksPerDay, - startDate, - topQueries, - topQueriesNoResults, - topQueriesWithClicks, - topQueriesNoClicks, - recentQueries, - } = useValues(AnalyticsLogic); - - return ( - - - - - - - - - - - - - - - - - - - - {TOP_QUERIES}} - filled - action={} - > - - - - - {TOP_QUERIES_NO_RESULTS}} - filled - action={ - - } - > - - - - - - - - - - - {TOP_QUERIES_WITH_CLICKS}} - filled - action={ - - } - > - - - - - {TOP_QUERIES_NO_CLICKS}} - filled - action={ - - } - > - - - - - - - - {RECENT_QUERIES}} - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.recentQueriesDescription', - { defaultMessage: 'A view into queries happening right now.' } - )} - action={} - > - - - - ); -}; - -export const ViewAllButton: React.FC<{ to: string }> = ({ to }) => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.analytics.table.viewAllButtonLabel', { - defaultMessage: 'View all', - })} - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/index.ts deleted file mode 100644 index a67f785f39e5c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 { Analytics } from './analytics'; -export { TopQueries } from './top_queries'; -export { TopQueriesNoResults } from './top_queries_no_results'; -export { TopQueriesNoClicks } from './top_queries_no_clicks'; -export { TopQueriesWithClicks } from './top_queries_with_clicks'; -export { RecentQueries } from './recent_queries'; -export { QueryDetail } from './query_detail'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx deleted file mode 100644 index 0eb3002f6472f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../../__mocks__/react_router'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsCards, AnalyticsChart, QueryClicksTable } from '../components'; - -import { QueryDetail } from '.'; - -describe('QueryDetail', () => { - beforeEach(() => { - mockUseParams.mockReturnValue({ query: 'some-query' }); - - setMockValues({ - totalQueriesForQuery: 100, - queriesPerDayForQuery: [0, 5, 10], - }); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsLayout).prop('title')).toEqual('"some-query"'); - expect(wrapper.find(AnalyticsLayout).prop('breadcrumbs')).toEqual(['Query', 'some-query']); - - expect(wrapper.find(AnalyticsCards)).toHaveLength(1); - expect(wrapper.find(AnalyticsChart)).toHaveLength(1); - expect(wrapper.find(QueryClicksTable)).toHaveLength(1); - }); - - it('renders empty "" search titles correctly', () => { - mockUseParams.mockReturnValue({ query: '""' }); - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsLayout).prop('title')).toEqual('""'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx deleted file mode 100644 index 8c2819e86bf40..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiPanel, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '..'; -import { useDecodedParams } from '../../../utils/encode_path_params'; - -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSection, QueryClicksTable } from '../components'; - -const QUERY_DETAIL_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title', - { defaultMessage: 'Query' } -); - -export const QueryDetail: React.FC = () => { - const { query } = useDecodedParams(); - const queryTitle = query === '""' ? query : `"${query}"`; - - const { totalQueriesForQuery, queriesPerDayForQuery, startDate, topClicksForQuery } = - useValues(AnalyticsLogic); - - return ( - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx deleted file mode 100644 index 46f4e9d2f3ee0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { RecentQueriesTable } from '../components'; - -import { RecentQueries } from '.'; - -describe('RecentQueries', () => { - it('renders', () => { - setMockValues({ recentQueries: [] }); - const wrapper = shallow(); - - expect(wrapper.find(RecentQueriesTable)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx deleted file mode 100644 index df6efdf06bc26..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { AnalyticsLogic } from '..'; -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSearch, RecentQueriesTable } from '../components'; -import { RECENT_QUERIES } from '../constants'; - -export const RecentQueries: React.FC = () => { - const { recentQueries } = useValues(AnalyticsLogic); - - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx deleted file mode 100644 index f69d10f42d174..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AnalyticsTable } from '../components'; - -import { TopQueries } from '.'; - -describe('TopQueries', () => { - it('renders', () => { - setMockValues({ topQueries: [] }); - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsTable)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx deleted file mode 100644 index f43bb807140a5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { AnalyticsLogic } from '..'; -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSearch, AnalyticsTable } from '../components'; -import { TOP_QUERIES } from '../constants'; - -export const TopQueries: React.FC = () => { - const { topQueries } = useValues(AnalyticsLogic); - - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx deleted file mode 100644 index 6898d32879f9c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AnalyticsTable } from '../components'; - -import { TopQueriesNoClicks } from '.'; - -describe('TopQueriesNoClicks', () => { - it('renders', () => { - setMockValues({ topQueriesNoClicks: [] }); - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsTable)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx deleted file mode 100644 index 413ca7594a9bd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { AnalyticsLogic } from '..'; -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSearch, AnalyticsTable } from '../components'; -import { TOP_QUERIES_NO_CLICKS } from '../constants'; - -export const TopQueriesNoClicks: React.FC = () => { - const { topQueriesNoClicks } = useValues(AnalyticsLogic); - - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx deleted file mode 100644 index e644e338db034..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AnalyticsTable } from '../components'; - -import { TopQueriesNoResults } from '.'; - -describe('TopQueriesNoResults', () => { - it('renders', () => { - setMockValues({ topQueriesNoResults: [] }); - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsTable)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx deleted file mode 100644 index df76d8349f70c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { AnalyticsLogic } from '..'; -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSearch, AnalyticsTable } from '../components'; -import { TOP_QUERIES_NO_RESULTS } from '../constants'; - -export const TopQueriesNoResults: React.FC = () => { - const { topQueriesNoResults } = useValues(AnalyticsLogic); - - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx deleted file mode 100644 index c966624f14757..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AnalyticsTable } from '../components'; - -import { TopQueriesWithClicks } from '.'; - -describe('TopQueriesWithClicks', () => { - it('renders', () => { - setMockValues({ topQueriesWithClicks: [] }); - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsTable)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx deleted file mode 100644 index fcc87848fd58d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { AnalyticsLogic } from '..'; -import { AnalyticsLayout } from '../analytics_layout'; -import { AnalyticsSearch, AnalyticsTable } from '../components'; -import { TOP_QUERIES_WITH_CLICKS } from '../constants'; - -export const TopQueriesWithClicks: React.FC = () => { - const { topQueriesWithClicks } = useValues(AnalyticsLogic); - - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/__mocks__/api_log.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/__mocks__/api_log.mock.ts deleted file mode 100644 index 6106cb049c7a1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/__mocks__/api_log.mock.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 const mockApiLog = { - timestamp: '1970-01-01T12:00:00.000Z', - http_method: 'POST', - status: 200, - user_agent: 'Mozilla/5.0', - full_request_path: '/api/as/v1/engines/national-parks-demo/search.json', - request_body: '{"query":"test search"}', - response_body: - '{"meta":{"page":{"current":1,"total_pages":0,"total_results":0,"size":20}},"results":[]}', -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_flyout.test.tsx deleted file mode 100644 index 3ff22479f4013..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_flyout.test.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; -import { mockApiLog } from '../__mocks__/api_log.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyout, EuiBadge } from '@elastic/eui'; - -import { ApiLogFlyout, ApiLogHeading } from './api_log_flyout'; - -describe('ApiLogFlyout', () => { - const values = { - isFlyoutOpen: true, - apiLog: mockApiLog, - }; - const actions = { - closeFlyout: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find('h2').text()).toEqual('Request details'); - expect(wrapper.find(ApiLogHeading).last().dive().find('h3').text()).toEqual('Response body'); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('POST'); - }); - - it('closes the flyout', () => { - const wrapper = shallow(); - - wrapper.find(EuiFlyout).simulate('close'); - expect(actions.closeFlyout).toHaveBeenCalled(); - }); - - it('does not render if the flyout is not open', () => { - setMockValues({ ...values, isFlyoutOpen: false }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('does not render if a current apiLog has not been set', () => { - setMockValues({ ...values, apiLog: null }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_flyout.tsx deleted file mode 100644 index 9551e78b40095..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_flyout.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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, { FC, PropsWithChildren } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiPortal, - EuiFlyout, - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiBadge, - EuiHealth, - EuiText, - EuiCode, - EuiCodeBlock, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { getStatusColor, attemptToFormatJson } from '../utils'; - -import { ApiLogLogic } from '.'; - -export const ApiLogFlyout: React.FC = () => { - const { isFlyoutOpen, apiLog } = useValues(ApiLogLogic); - const { closeFlyout } = useActions(ApiLogLogic); - - if (!isFlyoutOpen) return null; - if (!apiLog) return null; - - return ( - - - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.flyout.title', { - defaultMessage: 'Request details', - })} -

-
-
- - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTitle', { - defaultMessage: 'Method', - })} - -
- {apiLog.http_method} -
-
- - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTitle', { - defaultMessage: 'Status', - })} - - {apiLog.status} - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.timestampTitle', { - defaultMessage: 'Timestamp', - })} - - {apiLog.timestamp} - -
- - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.userAgentTitle', { - defaultMessage: 'User agent', - })} - - - {apiLog.user_agent} - - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.requestPathTitle', { - defaultMessage: 'Request path', - })} - - - {apiLog.full_request_path} - - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.requestBodyTitle', { - defaultMessage: 'Request body', - })} - - - {attemptToFormatJson(apiLog.request_body)} - - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.responseBodyTitle', { - defaultMessage: 'Response body', - })} - - - {attemptToFormatJson(apiLog.response_body)} - -
-
-
- ); -}; - -export const ApiLogHeading: FC> = ({ children }) => ( - -

{children}

-
-); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_logic.test.tsx deleted file mode 100644 index 25e6803a9883b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_logic.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { mockApiLog } from '../__mocks__/api_log.mock'; - -import { ApiLogLogic } from '.'; - -describe('ApiLogLogic', () => { - const { mount } = new LogicMounter(ApiLogLogic); - - const DEFAULT_VALUES = { - isFlyoutOpen: false, - apiLog: null, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(ApiLogLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('openFlyout', () => { - it('sets isFlyoutOpen to true & sets the current apiLog', () => { - mount({ isFlyoutOpen: false, apiLog: null }); - ApiLogLogic.actions.openFlyout(mockApiLog); - - expect(ApiLogLogic.values).toEqual({ - ...DEFAULT_VALUES, - isFlyoutOpen: true, - apiLog: mockApiLog, - }); - }); - }); - - describe('closeFlyout', () => { - it('sets isFlyoutOpen to false & resets the current apiLog', () => { - mount({ isFlyoutOpen: true, apiLog: mockApiLog }); - ApiLogLogic.actions.closeFlyout(); - - expect(ApiLogLogic.values).toEqual({ - ...DEFAULT_VALUES, - isFlyoutOpen: false, - apiLog: null, - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_logic.ts deleted file mode 100644 index 951c66dbe5f70..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/api_log_logic.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { ApiLog } from '../types'; - -interface ApiLogValues { - isFlyoutOpen: boolean; - apiLog: ApiLog | null; -} - -interface ApiLogActions { - openFlyout(apiLog: ApiLog): { apiLog: ApiLog }; - closeFlyout(): void; -} - -export const ApiLogLogic = kea>({ - path: ['enterprise_search', 'app_search', 'api_log_logic'], - actions: () => ({ - openFlyout: (apiLog) => ({ apiLog }), - closeFlyout: true, - }), - reducers: () => ({ - isFlyoutOpen: [ - false, - { - openFlyout: () => true, - closeFlyout: () => false, - }, - ], - apiLog: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - openFlyout: (_, { apiLog }) => apiLog, - closeFlyout: () => null, - }, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/index.ts deleted file mode 100644 index dcf949d9bf222..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_log/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { ApiLogFlyout } from './api_log_flyout'; -export { ApiLogLogic } from './api_log_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx deleted file mode 100644 index 94aa8e6faeae0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { rerender, getPageTitle } from '../../../test_helpers'; -import { LogRetentionCallout, LogRetentionTooltip } from '../log_retention'; - -import { ApiLogsTable, NewApiEventsPrompt } from './components'; - -import { ApiLogs } from '.'; - -describe('ApiLogs', () => { - const values = { - dataLoading: false, - apiLogs: [], - meta: { page: { current: 1 } }, - }; - const actions = { - fetchApiLogs: jest.fn(), - pollForApiLogs: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(getPageTitle(wrapper)).toEqual('API Logs'); - expect(wrapper.find(ApiLogsTable)).toHaveLength(1); - expect(wrapper.find(NewApiEventsPrompt)).toHaveLength(1); - - expect(wrapper.find(LogRetentionCallout).prop('type')).toEqual('api'); - expect(wrapper.find(LogRetentionTooltip).prop('type')).toEqual('api'); - }); - - describe('loading state', () => { - it('renders a full-page loading state on initial page load (no logs exist yet)', () => { - setMockValues({ ...values, dataLoading: true, apiLogs: [] }); - const wrapper = shallow(); - - expect(wrapper.prop('isLoading')).toEqual(true); - }); - - it('does not re-render a full-page loading state after initial page load (uses component-level loading state instead)', () => { - setMockValues({ ...values, dataLoading: true, apiLogs: [{}] }); - const wrapper = shallow(); - - expect(wrapper.prop('isLoading')).toEqual(false); - }); - }); - - describe('effects', () => { - it('calls a manual fetchApiLogs on page load and pagination', () => { - const wrapper = shallow(); - expect(actions.fetchApiLogs).toHaveBeenCalledTimes(1); - - setMockValues({ ...values, meta: { page: { current: 2 } } }); - rerender(wrapper); - - expect(actions.fetchApiLogs).toHaveBeenCalledTimes(2); - }); - - it('starts pollForApiLogs on page load', () => { - shallow(); - expect(actions.pollForApiLogs).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx deleted file mode 100644 index 06bf16cbcbddf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; - -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; -import { LogRetentionCallout, LogRetentionTooltip, LogRetentionOptions } from '../log_retention'; - -import { ApiLogFlyout } from './api_log'; -import { ApiLogsTable, NewApiEventsPrompt, EmptyState } from './components'; -import { API_LOGS_TITLE, RECENT_API_EVENTS } from './constants'; - -import { ApiLogsLogic } from '.'; - -export const ApiLogs: React.FC = () => { - const { dataLoading, apiLogs, meta } = useValues(ApiLogsLogic); - const { fetchApiLogs, pollForApiLogs } = useActions(ApiLogsLogic); - - useEffect(() => { - fetchApiLogs(); - }, [meta.page.current]); - - useEffect(() => { - pollForApiLogs(); - }, []); - - return ( - } - > - - - - - - -

{RECENT_API_EVENTS}

-
-
- - - - - - - -
- - - - -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts deleted file mode 100644 index 8e2e1383a3ac8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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 { mockApiLog } from './__mocks__/api_log.mock'; -import { - LogicMounter, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { DEFAULT_META } from '../../../shared/constants'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { ApiLogsLogic } from '.'; - -describe('ApiLogsLogic', () => { - const { mount, unmount } = new LogicMounter(ApiLogsLogic); - const { http } = mockHttpValues; - const { flashErrorToast } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - dataLoading: true, - apiLogs: [], - meta: DEFAULT_META, - hasNewData: false, - polledData: {}, - intervalId: null, - }; - - const MOCK_API_RESPONSE = { - results: [mockApiLog, mockApiLog], - meta: { - page: { - current: 1, - total_pages: 10, - total_results: 100, - size: 10, - }, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(ApiLogsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onPollStart', () => { - it('sets intervalId state', () => { - mount(); - ApiLogsLogic.actions.onPollStart(123); - - expect(ApiLogsLogic.values).toEqual({ - ...DEFAULT_VALUES, - intervalId: 123, - }); - }); - }); - - describe('storePolledData', () => { - it('sets hasNewData to true & polledData state', () => { - mount({ hasNewData: false }); - ApiLogsLogic.actions.storePolledData(MOCK_API_RESPONSE); - - expect(ApiLogsLogic.values).toEqual({ - ...DEFAULT_VALUES, - hasNewData: true, - polledData: MOCK_API_RESPONSE, - }); - }); - }); - - describe('updateView', () => { - it('sets dataLoading & hasNewData to false, sets apiLogs & meta state', () => { - mount({ dataLoading: true, hasNewData: true }); - ApiLogsLogic.actions.updateView(MOCK_API_RESPONSE); - - expect(ApiLogsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - hasNewData: false, - apiLogs: MOCK_API_RESPONSE.results, - meta: MOCK_API_RESPONSE.meta, - }); - }); - }); - - describe('onPaginate', () => { - it('sets dataLoading to true & sets meta state', () => { - mount({ dataLoading: false }); - ApiLogsLogic.actions.onPaginate(5); - - expect(ApiLogsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - meta: { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - current: 5, - }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('pollForApiLogs', () => { - jest.useFakeTimers({ legacyFakeTimers: true }); - const setIntervalSpy = jest.spyOn(global, 'setInterval'); - - it('starts a poll that calls fetchApiLogs at set intervals', () => { - mount(); - jest.spyOn(ApiLogsLogic.actions, 'onPollStart'); - jest.spyOn(ApiLogsLogic.actions, 'fetchApiLogs'); - - ApiLogsLogic.actions.pollForApiLogs(); - expect(setIntervalSpy).toHaveBeenCalled(); - expect(ApiLogsLogic.actions.onPollStart).toHaveBeenCalled(); - - jest.advanceTimersByTime(5000); - expect(ApiLogsLogic.actions.fetchApiLogs).toHaveBeenCalledWith({ isPoll: true }); - }); - - it('does not create new polls if one already exists', () => { - mount({ intervalId: 123 }); - ApiLogsLogic.actions.pollForApiLogs(); - expect(setIntervalSpy).not.toHaveBeenCalled(); - }); - - afterAll(() => jest.useRealTimers); - }); - - describe('fetchApiLogs', () => { - const mockDate = jest - .spyOn(global.Date, 'now') - .mockImplementation(() => new Date('1970-01-02').valueOf()); - - afterAll(() => mockDate.mockRestore()); - - it('should make an API call', () => { - mount(); - - ApiLogsLogic.actions.fetchApiLogs(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/api_logs', { - query: { - 'page[current]': 1, - 'filters[date][from]': '1970-01-01T00:00:00.000Z', - 'filters[date][to]': '1970-01-02T00:00:00.000Z', - sort_direction: 'desc', - }, - }); - }); - - describe('manual fetch (page load & pagination)', () => { - it('updates the view immediately with the returned data', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_API_RESPONSE)); - mount(); - jest.spyOn(ApiLogsLogic.actions, 'updateView'); - - ApiLogsLogic.actions.fetchApiLogs(); - await nextTick(); - - expect(ApiLogsLogic.actions.updateView).toHaveBeenCalledWith(MOCK_API_RESPONSE); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - ApiLogsLogic.actions.fetchApiLogs(); - }); - }); - - describe('poll fetch (interval)', () => { - it('does not automatically update the view', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_API_RESPONSE)); - mount({ dataLoading: false }); - jest.spyOn(ApiLogsLogic.actions, 'onPollInterval'); - - ApiLogsLogic.actions.fetchApiLogs({ isPoll: true }); - await nextTick(); - - expect(ApiLogsLogic.actions.onPollInterval).toHaveBeenCalledWith(MOCK_API_RESPONSE); - }); - - it('sets a custom error message on poll error', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); - mount({ dataLoading: false }); - - ApiLogsLogic.actions.fetchApiLogs({ isPoll: true }); - await nextTick(); - - expect(flashErrorToast).toHaveBeenCalledWith('Could not refresh API log data', { - text: expect.stringContaining('Please check your connection'), - toastLifeTimeMs: 3750, - }); - }); - }); - - describe('when a manual fetch and a poll fetch occur at the same time', () => { - it('should short-circuit polls in favor of manual fetches', async () => { - // dataLoading is the signal we're using to check for a manual fetch - mount({ dataLoading: true }); - jest.spyOn(ApiLogsLogic.actions, 'onPollInterval'); - - ApiLogsLogic.actions.fetchApiLogs({ isPoll: true }); - await nextTick(); - - expect(http.get).not.toHaveBeenCalled(); - expect(ApiLogsLogic.actions.onPollInterval).not.toHaveBeenCalled(); - }); - }); - }); - - describe('onPollInterval', () => { - describe('when API logs are empty and new polled data comes in', () => { - it('updates the view immediately with the returned data (no manual action required)', () => { - mount({ meta: { page: { total_results: 0 } } }); - jest.spyOn(ApiLogsLogic.actions, 'updateView'); - - ApiLogsLogic.actions.onPollInterval(MOCK_API_RESPONSE); - - expect(ApiLogsLogic.actions.updateView).toHaveBeenCalledWith(MOCK_API_RESPONSE); - }); - }); - - describe('when previous API logs already exist on the page', () => { - describe('when new data is returned', () => { - it('stores the new polled data', () => { - mount({ meta: { page: { total_results: 1 } } }); - jest.spyOn(ApiLogsLogic.actions, 'storePolledData'); - - ApiLogsLogic.actions.onPollInterval(MOCK_API_RESPONSE); - - expect(ApiLogsLogic.actions.storePolledData).toHaveBeenCalledWith(MOCK_API_RESPONSE); - }); - }); - - describe('when the same data is returned', () => { - it('does nothing', () => { - mount({ meta: { page: { total_results: 100 } } }); - jest.spyOn(ApiLogsLogic.actions, 'updateView'); - jest.spyOn(ApiLogsLogic.actions, 'storePolledData'); - - ApiLogsLogic.actions.onPollInterval(MOCK_API_RESPONSE); - - expect(ApiLogsLogic.actions.updateView).not.toHaveBeenCalled(); - expect(ApiLogsLogic.actions.storePolledData).not.toHaveBeenCalled(); - }); - }); - }); - }); - - describe('onUserRefresh', () => { - it('updates the apiLogs data with the stored polled data', () => { - mount({ apiLogs: [], polledData: MOCK_API_RESPONSE }); - - ApiLogsLogic.actions.onUserRefresh(); - - expect(ApiLogsLogic.values).toEqual({ - ...DEFAULT_VALUES, - apiLogs: MOCK_API_RESPONSE.results, - meta: MOCK_API_RESPONSE.meta, - polledData: MOCK_API_RESPONSE, - dataLoading: false, - }); - }); - }); - }); - - describe('events', () => { - describe('unmount', () => { - const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); - - it('clears the poll interval', () => { - mount({ intervalId: 123 }); - unmount(); - expect(clearIntervalSpy).toHaveBeenCalledWith(123); - }); - - it('does not clearInterval if a poll has not been started', () => { - mount({ intervalId: null }); - unmount(); - expect(clearIntervalSpy).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts deleted file mode 100644 index cf9afa51606ea..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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 { DEFAULT_META } from '../../../shared/constants'; -import { flashAPIErrors, flashErrorToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { updateMetaPageIndex } from '../../../shared/table_pagination'; -import { EngineLogic } from '../engine'; - -import { POLLING_DURATION, POLLING_ERROR_TITLE, POLLING_ERROR_TEXT } from './constants'; -import { ApiLogsData, ApiLog } from './types'; -import { getDateString } from './utils'; - -interface ApiLogsValues { - dataLoading: boolean; - apiLogs: ApiLog[]; - meta: ApiLogsData['meta']; - hasNewData: boolean; - polledData: ApiLogsData; - intervalId: number | null; -} - -interface ApiLogsActions { - fetchApiLogs(options?: { isPoll: boolean }): { isPoll: boolean }; - pollForApiLogs(): void; - onPollStart(intervalId: number): { intervalId: number }; - onPollInterval(data: ApiLogsData): ApiLogsData; - storePolledData(data: ApiLogsData): ApiLogsData; - updateView(data: ApiLogsData): ApiLogsData; - onUserRefresh(): void; - onPaginate(newPageIndex: number): { newPageIndex: number }; -} - -export const ApiLogsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'api_logs_logic'], - actions: () => ({ - fetchApiLogs: ({ isPoll } = { isPoll: false }) => ({ isPoll }), - pollForApiLogs: true, - onPollStart: (intervalId) => ({ intervalId }), - onPollInterval: ({ results, meta }) => ({ results, meta }), - storePolledData: ({ results, meta }) => ({ results, meta }), - updateView: ({ results, meta }) => ({ results, meta }), - onUserRefresh: true, - onPaginate: (newPageIndex) => ({ newPageIndex }), - }), - reducers: () => ({ - dataLoading: [ - true, - { - updateView: () => false, - onPaginate: () => true, - }, - ], - apiLogs: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - updateView: (_, { results }) => results, - }, - ], - meta: [ - DEFAULT_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - updateView: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - hasNewData: [ - false, - { - storePolledData: () => true, - updateView: () => false, - }, - ], - polledData: [ - {} as ApiLogsData, - { - // @ts-expect-error upgrade typescript v5.1.6 - storePolledData: (_, data) => data, - }, - ], - intervalId: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onPollStart: (_, { intervalId }) => intervalId, - }, - ], - }), - listeners: ({ actions, values }) => ({ - pollForApiLogs: () => { - if (values.intervalId) return; // Ensure we only have one poll at a time - - const id = window.setInterval(() => actions.fetchApiLogs({ isPoll: true }), POLLING_DURATION); - actions.onPollStart(id); - }, - fetchApiLogs: async ({ isPoll }) => { - if (isPoll && values.dataLoading) return; // Manual fetches (i.e. user pagination) should override polling - - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/api_logs`, - { - query: { - 'page[current]': values.meta.page.current, - 'filters[date][from]': getDateString(-1), - 'filters[date][to]': getDateString(), - sort_direction: 'desc', - }, - } - ); - - // Manual fetches (e.g. page load, user pagination) should update the view immediately, - // while polls are stored in-state until the user manually triggers the 'Refresh' action - if (isPoll) { - actions.onPollInterval(response); - } else { - actions.updateView(response); - } - } catch (e) { - if (isPoll) { - // If polling fails, it will typically be due to http connection - - // we should send a more human-readable message if so - flashErrorToast(POLLING_ERROR_TITLE, { - text: POLLING_ERROR_TEXT, - toastLifeTimeMs: POLLING_DURATION * 0.75, - }); - } else { - flashAPIErrors(e); - } - } - }, - onPollInterval: (data, breakpoint) => { - breakpoint(); // Prevents errors if logic unmounts while fetching - - const previousResults = values.meta.page.total_results; - const newResults = data.meta.page.total_results; - const isEmpty = previousResults === 0; - const hasNewData = previousResults !== newResults; - - if (isEmpty && hasNewData) { - actions.updateView(data); // Empty logs should automatically update with new data without a manual action - } else if (hasNewData) { - actions.storePolledData(data); // Otherwise, store any new data until the user manually refreshes the table - } - }, - onUserRefresh: () => { - actions.updateView(values.polledData); - }, - }), - events: ({ values }) => ({ - beforeUnmount() { - if (values.intervalId !== null) clearInterval(values.intervalId); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.scss deleted file mode 100644 index 44834d81a13c6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.scss +++ /dev/null @@ -1,5 +0,0 @@ -.apiLogDetailButton { - // More closely mimics the regular line height of an EuiLink / - // compresses table rows back to the standard height - height: $euiSizeL !important; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx deleted file mode 100644 index c1ee67cb32477..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -// NOTE: We're mocking FormattedRelative here because it (currently) has -// console warn issues, and it allows us to skip mocking dates -jest.mock('@kbn/i18n-react', () => { - const { i18n } = jest.requireActual('@kbn/i18n'); - i18n.init({ locale: 'en' }); - - return { - ...(jest.requireActual('@kbn/i18n-react') as object), - FormattedRelative: jest.fn(() => '20 hours ago'), - }; -}); - -import React from 'react'; - -import { EuiBasicTable, EuiBadge, EuiHealth, EuiButtonEmpty } from '@elastic/eui'; - -import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; - -import { DEFAULT_META } from '../../../../shared/constants'; - -import { ApiLogsTable } from '.'; - -describe('ApiLogsTable', () => { - const apiLogs = [ - { - timestamp: '1970-01-01T00:00:00.000Z', - status: 404, - http_method: 'GET', - full_request_path: '/api/as/v1/test', - }, - { - timestamp: '1970-01-01T00:00:00.000Z', - status: 500, - http_method: 'DELETE', - full_request_path: '/api/as/v1/test', - }, - { - timestamp: '1970-01-01T00:00:00.000Z', - status: 200, - http_method: 'POST', - full_request_path: '/api/as/v1/engines/some-engine/search', - }, - ]; - - const values = { - dataLoading: false, - apiLogs, - meta: DEFAULT_META, - }; - const actions = { - onPaginate: jest.fn(), - openFlyout: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = mountWithIntl(); - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Method'); - expect(tableContent).toContain('GET'); - expect(tableContent).toContain('DELETE'); - expect(tableContent).toContain('POST'); - expect(wrapper.find(EuiBadge)).toHaveLength(3); - - expect(tableContent).toContain('Time'); - expect(tableContent).toContain('20 hours ago'); - - expect(tableContent).toContain('Endpoint'); - expect(tableContent).toContain('/api/as/v1/test'); - expect(tableContent).toContain('/api/as/v1/engines/some-engine/search'); - - expect(tableContent).toContain('Status'); - expect(tableContent).toContain('404'); - expect(tableContent).toContain('500'); - expect(tableContent).toContain('200'); - expect(wrapper.find(EuiHealth)).toHaveLength(3); - - expect(wrapper.find(EuiButtonEmpty)).toHaveLength(3); - wrapper.find('[data-test-subj="ApiLogsTableDetailsButton"]').first().simulate('click'); - expect(actions.openFlyout).toHaveBeenCalled(); - }); - - describe('hasPagination', () => { - it('does not render with pagination by default', () => { - const wrapper = shallowWithIntl(); - - expect(wrapper.find(EuiBasicTable).prop('pagination')).toBeFalsy(); - }); - - it('renders pagination if hasPagination is true', () => { - const wrapper = shallowWithIntl(); - - expect(wrapper.find(EuiBasicTable).prop('pagination')).toBeTruthy(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx deleted file mode 100644 index 3686ce0926eb5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/api_logs_table.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiBadge, - EuiHealth, - EuiButtonEmpty, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedRelative } from '@kbn/i18n-react'; - -import { ApiLogsLogic } from '..'; -import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; - -import { ApiLogLogic } from '../api_log'; -import { ApiLog } from '../types'; -import { getStatusColor } from '../utils'; - -import { EmptyState } from '.'; - -import './api_logs_table.scss'; - -interface Props { - hasPagination?: boolean; -} -export const ApiLogsTable: React.FC = ({ hasPagination }) => { - const { dataLoading, apiLogs, meta } = useValues(ApiLogsLogic); - const { onPaginate } = useActions(ApiLogsLogic); - const { openFlyout } = useActions(ApiLogLogic); - - const columns: Array> = [ - { - field: 'http_method', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.methodTableHeading', { - defaultMessage: 'Method', - }), - width: '100px', - render: (method: string) => {method}, - }, - { - field: 'timestamp', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.timeTableHeading', { - defaultMessage: 'Time', - }), - width: '20%', - render: (dateString: string) => , - }, - { - field: 'full_request_path', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.endpointTableHeading', { - defaultMessage: 'Endpoint', - }), - width: '50%', - truncateText: true, - mobileOptions: { - // @ts-ignore - EUI's typing is incorrect here - width: '100%', - }, - }, - { - field: 'status', - name: i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.statusTableHeading', { - defaultMessage: 'Status', - }), - dataType: 'number', - width: '100px', - render: (status: number) => {status}, - }, - { - width: '100px', - align: 'right', - render: (apiLog: ApiLog) => ( - openFlyout(apiLog)} - > - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.detailsButtonLabel', { - defaultMessage: 'Details', - })} - - ), - }, - ]; - - const paginationProps = hasPagination - ? { - pagination: { - ...convertMetaToPagination(meta), - showPerPageOptions: false, - }, - onChange: handlePageChange(onPaginate), - } - : {}; - - return ( - } - {...paginationProps} - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx deleted file mode 100644 index 46aabf57da0c7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('No API events in the last 24 hours'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchApis) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx deleted file mode 100644 index bf8cf009759d0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyState: React.FC = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyTitle', { - defaultMessage: 'No API events in the last 24 hours', - })} - - } - body={ -

- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.emptyDescription', { - defaultMessage: 'Logs will update in real-time when an API request occurs.', - })} -

- } - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel', { - defaultMessage: 'View the API reference', - })} - - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts deleted file mode 100644 index 863216554a540..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { ApiLogsTable } from './api_logs_table'; -export { NewApiEventsPrompt } from './new_api_events_prompt'; -export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.scss deleted file mode 100644 index 0f033bd37c61c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.scss +++ /dev/null @@ -1,6 +0,0 @@ -.newApiEventsPrompt { - padding: $euiSizeXS; - padding-left: $euiSizeS; - display: flex; - align-items: center; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.test.tsx deleted file mode 100644 index 1f7f6f7f96488..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButtonEmpty } from '@elastic/eui'; - -import { NewApiEventsPrompt } from '.'; - -describe('NewApiEventsPrompt', () => { - const values = { - hasNewData: true, - }; - const actions = { - onUserRefresh: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('does not render if no new data has been polled', () => { - setMockValues({ ...values, hasNewData: false }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('calls onUserRefresh', () => { - const wrapper = shallow(); - - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(actions.onUserRefresh).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.tsx deleted file mode 100644 index 8ac6b3a921ee0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/new_api_events_prompt.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiPanel, EuiButtonEmpty } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ApiLogsLogic } from '..'; - -import './new_api_events_prompt.scss'; - -export const NewApiEventsPrompt: React.FC = () => { - const { hasNewData } = useValues(ApiLogsLogic); - const { onUserRefresh } = useActions(ApiLogsLogic); - - return hasNewData ? ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsMessage', { - defaultMessage: 'New events have been logged.', - })} - - {i18n.translate('xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsButtonLabel', { - defaultMessage: 'Refresh', - })} - - - ) : null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts deleted file mode 100644 index ac1fbff150723..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const API_LOGS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.apiLogs.title', - { defaultMessage: 'API Logs' } -); - -export const RECENT_API_EVENTS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.apiLogs.recent', - { defaultMessage: 'Recent API events' } -); - -export const POLLING_DURATION = 5000; - -export const POLLING_ERROR_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorMessage', - { defaultMessage: 'Could not refresh API log data' } -); -export const POLLING_ERROR_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.apiLogs.pollingErrorDescription', - { defaultMessage: 'Please check your connection or manually reload the page.' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts deleted file mode 100644 index 568026dab231f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { API_LOGS_TITLE } from './constants'; -export { ApiLogsTable, NewApiEventsPrompt } from './components'; -export { ApiLogFlyout } from './api_log'; -export { ApiLogs } from './api_logs'; -export { ApiLogsLogic } from './api_logs_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts deleted file mode 100644 index 05c0d11d03240..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; - -export interface ApiLog { - timestamp: string; // Date ISO string - status: number; - http_method: string; - full_request_path: string; - user_agent: string; - request_body: string; // JSON string - response_body: string; // JSON string - // NOTE: The API also sends us back `path: null`, but we don't appear to be - // using it anywhere, so I've opted not to list it in our types -} - -export interface ApiLogsData { - results: ApiLog[]; - meta: Meta; - // NOTE: The API sends us back even more `meta` data than the normal (sort_direction, filters, query), - // but we currently don't use that data in our front-end code, so I'm opting not to list them in our types -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts deleted file mode 100644 index 5a9007e3b66e3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 dedent from 'dedent'; - -import { getDateString, getStatusColor, attemptToFormatJson } from './utils'; - -describe('getDateString', () => { - const mockDate = jest - .spyOn(global.Date, 'now') - .mockImplementation(() => new Date('1970-01-02').valueOf()); - - it('gets the current date in ISO format', () => { - expect(getDateString()).toEqual('1970-01-02T00:00:00.000Z'); - }); - - it('allows passing a number of days to offset the timestamp by', () => { - expect(getDateString(-1)).toEqual('1970-01-01T00:00:00.000Z'); - expect(getDateString(10)).toEqual('1970-01-12T00:00:00.000Z'); - }); - - afterAll(() => mockDate.mockRestore()); -}); - -describe('getStatusColor', () => { - it('returns a valid EUI badge color based on the status code', () => { - expect(getStatusColor(200)).toEqual('success'); - expect(getStatusColor(301)).toEqual('primary'); - expect(getStatusColor(404)).toEqual('warning'); - expect(getStatusColor(503)).toEqual('danger'); - }); -}); - -describe('attemptToFormatJson', () => { - it('takes an unformatted JSON string and correctly newlines/indents it', () => { - expect(attemptToFormatJson('{"hello":"world","lorem":{"ipsum":"dolor","sit":"amet"}}')) - .toEqual(dedent`{ - "hello": "world", - "lorem": { - "ipsum": "dolor", - "sit": "amet" - } - }`); - }); - - it('returns the original content if it is not properly formatted JSON', () => { - expect(attemptToFormatJson('{invalid json}')).toEqual('{invalid json}'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts deleted file mode 100644 index 9c0a186daddb1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 const getDateString = (offSetDays?: number) => { - const date = new Date(Date.now()); - if (offSetDays) date.setDate(date.getDate() + offSetDays); - return date.toISOString(); -}; - -export const getStatusColor = (status: number) => { - let color = ''; - if (status >= 100 && status < 300) color = 'success'; - if (status >= 300 && status < 400) color = 'primary'; - if (status >= 400 && status < 500) color = 'warning'; - if (status >= 500) color = 'danger'; - return color; -}; - -export const attemptToFormatJson = (possibleJson: string) => { - try { - // it is JSON, we can format it with newlines/indentation - return JSON.stringify(JSON.parse(possibleJson), null, 2); - } catch { - // if it's not JSON, we return the original content - return possibleJson; - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx deleted file mode 100644 index c2385efe47061..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx +++ /dev/null @@ -1,613 +0,0 @@ -/* - * 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: ( - <> - {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 deleted file mode 100644 index 493eaae3e20bd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 deleted file mode 100644 index a79904385de94..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_api_logic.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 deleted file mode 100644 index bb3f94a37d9e5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0cce74ef59de1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate_logic.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 deleted file mode 100644 index b25c4baf1e00c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gated_form_page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 deleted file mode 100644 index c724e9ab999e8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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/crawler/assets/bg_crawler_landing.png b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/assets/bg_crawler_landing.png deleted file mode 100644 index e1f14ac26f353..0000000000000 Binary files a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/assets/bg_crawler_landing.png and /dev/null differ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_flyout.test.tsx deleted file mode 100644 index bdedc9357fa0e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_flyout.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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, ShallowWrapper } from 'enzyme'; - -import { EuiButton, EuiButtonEmpty, EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; - -import { AddDomainFlyout } from './add_domain_flyout'; -import { AddDomainForm } from './add_domain_form'; -import { AddDomainFormErrors } from './add_domain_form_errors'; -import { AddDomainFormSubmitButton } from './add_domain_form_submit_button'; - -describe('AddDomainFlyout', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('is hidden by default', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFlyout)).toHaveLength(0); - }); - - it('displays the flyout when the button is pressed', () => { - const wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - - expect(wrapper.find(EuiFlyout)).toHaveLength(1); - }); - - describe('flyout', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - }); - - it('displays form errors', () => { - expect(wrapper.find(EuiFlyoutBody).dive().find(AddDomainFormErrors)).toHaveLength(1); - }); - - it('contains a form to add domains', () => { - expect(wrapper.find(AddDomainForm)).toHaveLength(1); - }); - - it('contains a cancel buttonn', () => { - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(wrapper.find(EuiFlyout)).toHaveLength(0); - }); - - it('contains a submit button', () => { - expect(wrapper.find(AddDomainFormSubmitButton)).toHaveLength(1); - }); - - it('hides the flyout on close', () => { - wrapper.find(EuiFlyout).simulate('close'); - - expect(wrapper.find(EuiFlyout)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_flyout.tsx deleted file mode 100644 index f953e8e0fce39..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_flyout.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiPortal, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; - -import { AddDomainForm } from './add_domain_form'; -import { AddDomainFormErrors } from './add_domain_form_errors'; -import { AddDomainFormSubmitButton } from './add_domain_form_submit_button'; - -export const AddDomainFlyout: React.FC = () => { - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - - return ( - <> - setIsFlyoutVisible(true)} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.openButtonLabel', - { - defaultMessage: 'Add domain', - } - )} - - - {isFlyoutVisible && ( - - setIsFlyoutVisible(false)}> - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.title', - { - defaultMessage: 'Add a new domain', - } - )} -

-
-
- - - - - } - > - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainFlyout.description', - { - defaultMessage: - 'You can add multiple domains to this engine\'s web crawler. Add another domain here and modify the entry points and crawl rules from the "Manage" page.', - } - )} -

- - - - - - - - setIsFlyoutVisible(false)}> - {CANCEL_BUTTON_LABEL} - - - - - - - - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form.test.tsx deleted file mode 100644 index f653ed801ac83..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton, EuiFieldText, EuiForm } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n-react'; - -import { rerender } from '../../../../../test_helpers'; - -import { AddDomainForm } from './add_domain_form'; -import { AddDomainValidation } from './add_domain_validation'; - -const MOCK_VALUES = { - addDomainFormInputValue: 'https://', - entryPointValue: '/', - isValidationLoading: false, - hasValidationCompleted: false, -}; - -const MOCK_ACTIONS = { - setAddDomainFormInputValue: jest.fn(), - startDomainValidation: jest.fn(), -}; - -describe('AddDomainForm', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.find(EuiForm)).toHaveLength(1); - }); - - it('contains a submit button', () => { - expect(wrapper.find(EuiButton).prop('type')).toEqual('submit'); - }); - - it('validates domain on submit', () => { - wrapper.find(EuiForm).simulate('submit', { preventDefault: jest.fn() }); - - expect(MOCK_ACTIONS.startDomainValidation).toHaveBeenCalledTimes(1); - }); - - describe('url field', () => { - it('uses the value from the logic', () => { - setMockValues({ - ...MOCK_VALUES, - addDomainFormInputValue: 'test value', - }); - - rerender(wrapper); - - expect(wrapper.find(EuiFieldText).prop('value')).toEqual('test value'); - }); - - it('sets the value in the logic on change', () => { - wrapper.find(EuiFieldText).simulate('change', { target: { value: 'test value' } }); - - expect(MOCK_ACTIONS.setAddDomainFormInputValue).toHaveBeenCalledWith('test value'); - }); - }); - - describe('validate domain button', () => { - it('is enabled when the input has a value', () => { - setMockValues({ - ...MOCK_VALUES, - addDomainFormInputValue: 'https://elastic.co', - }); - - rerender(wrapper); - - expect(wrapper.find(EuiButton).prop('disabled')).toEqual(false); - }); - - it('is disabled when the input value is empty', () => { - setMockValues({ - ...MOCK_VALUES, - addDomainFormInputValue: '', - }); - - rerender(wrapper); - - expect(wrapper.find(EuiButton).prop('disabled')).toEqual(true); - }); - }); - - describe('entry point indicator', () => { - it('is hidden when the entry point is /', () => { - setMockValues({ - ...MOCK_VALUES, - entryPointValue: '/', - }); - - rerender(wrapper); - - expect(wrapper.find(FormattedMessage)).toHaveLength(0); - }); - - it('displays the entry point otherwise', () => { - setMockValues({ - ...MOCK_VALUES, - entryPointValue: '/guide', - }); - - rerender(wrapper); - - expect(wrapper.find(FormattedMessage)).toHaveLength(1); - }); - }); - - describe('validation', () => { - it('is hidden by default', () => { - expect(wrapper.find(AddDomainValidation)).toHaveLength(0); - }); - - it('can be shown to the user', () => { - setMockValues({ - ...MOCK_VALUES, - displayValidation: true, - }); - - rerender(wrapper); - - expect(wrapper.find(AddDomainValidation)).toHaveLength(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form.tsx deleted file mode 100644 index 6e0f18597db33..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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, - EuiCode, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiFieldText, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { AddDomainLogic } from './add_domain_logic'; -import { AddDomainValidation } from './add_domain_validation'; - -export const AddDomainForm: React.FC = () => { - const { setAddDomainFormInputValue, startDomainValidation } = useActions(AddDomainLogic); - - const { addDomainFormInputValue, displayValidation, entryPointValue } = useValues(AddDomainLogic); - - return ( - <> - { - event.preventDefault(); - startDomainValidation(); - }} - component="form" - > - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.urlHelpText', - { - defaultMessage: 'Domain URLs require a protocol and cannot contain any paths.', - } - )} - - } - > - - - setAddDomainFormInputValue(e.target.value)} - fullWidth - /> - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.validateButtonLabel', - { - defaultMessage: 'Validate Domain', - } - )} - - - - - - {entryPointValue !== '/' && ( - <> - - -

- - {entryPointValue}, - }} - /> - -

-
- - )} - - {displayValidation && } - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_errors.test.tsx deleted file mode 100644 index d2c3ac37d58fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_errors.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AddDomainFormErrors } from './add_domain_form_errors'; - -describe('AddDomainFormErrors', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('is empty when there are no errors', () => { - setMockValues({ - errors: [], - }); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('displays all the errors from the logic', () => { - setMockValues({ - errors: ['first error', 'second error'], - }); - - const wrapper = shallow(); - - expect(wrapper.find('p')).toHaveLength(2); - expect(wrapper.find('p').first().text()).toContain('first error'); - expect(wrapper.find('p').last().text()).toContain('second error'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_errors.tsx deleted file mode 100644 index d3117c006c149..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_errors.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiCallOut } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AddDomainLogic } from './add_domain_logic'; - -export const AddDomainFormErrors: React.FC = () => { - const { errors } = useValues(AddDomainLogic); - - if (errors.length > 0) { - return ( - - {errors.map((message, index) => ( -

{message}

- ))} -
- ); - } - - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_submit_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_submit_button.test.tsx deleted file mode 100644 index 1926c4c28d3c9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_submit_button.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { AddDomainFormSubmitButton } from './add_domain_form_submit_button'; - -describe('AddDomainFormSubmitButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('is disabled when the domain has not been validated', () => { - setMockValues({ - allowSubmit: false, - }); - - const wrapper = shallow(); - - expect(wrapper.prop('disabled')).toBe(true); - }); - - it('is enabled when the domain has been validated', () => { - setMockValues({ - allowSubmit: true, - }); - - const wrapper = shallow(); - - expect(wrapper.prop('disabled')).toBe(false); - }); - - it('submits the domain on click', () => { - const submitNewDomain = jest.fn(); - - setMockActions({ - submitNewDomain, - }); - setMockValues({ - allowSubmit: true, - }); - - const wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - - expect(submitNewDomain).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_submit_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_submit_button.tsx deleted file mode 100644 index 46019284a7930..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_form_submit_button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { AddDomainLogic } from './add_domain_logic'; - -export const AddDomainFormSubmitButton: React.FC = () => { - const { submitNewDomain } = useActions(AddDomainLogic); - - const { allowSubmit } = useValues(AddDomainLogic); - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.addDomainForm.submitButtonLabel', { - defaultMessage: 'Add domain', - })} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts deleted file mode 100644 index 08bd78b5298d4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts +++ /dev/null @@ -1,784 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, - mockKibanaValues, -} from '../../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/engine_logic.mock'; - -jest.mock('../../crawler_logic', () => ({ - CrawlerLogic: { - actions: { - onReceiveCrawlerData: jest.fn(), - }, - }, -})); - -jest.mock('./utils', () => ({ - ...(jest.requireActual('./utils') as object), - getDomainWithProtocol: jest.fn().mockImplementation((domain) => domain), -})); - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { CrawlerLogic } from '../../crawler_logic'; -import { CrawlerDomain } from '../../types'; - -import { AddDomainLogic, AddDomainLogicValues } from './add_domain_logic'; -import { getDomainWithProtocol } from './utils'; - -const DEFAULT_VALUES: AddDomainLogicValues = { - addDomainFormInputValue: 'https://', - entryPointValue: '/', - canIgnoreValidationFailure: false, - displayValidation: false, - domainValidationResult: { - steps: { - contentVerification: { state: '' }, - indexingRestrictions: { state: '' }, - initialValidation: { state: '' }, - networkConnectivity: { state: '' }, - }, - }, - allowSubmit: false, - ignoreValidationFailure: false, - isValidationLoading: false, - hasBlockingFailure: false, - hasValidationCompleted: false, - errors: [], -}; - -describe('AddDomainLogic', () => { - const { mount } = new LogicMounter(AddDomainLogic); - const { flashSuccessToast } = mockFlashMessageHelpers; - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has default values', () => { - expect(AddDomainLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('clearDomainFormInputValue', () => { - beforeEach(() => { - mount({ - addDomainFormInputValue: 'http://elastic.co', - entryPointValue: '/foo', - hasValidationCompleted: true, - errors: ['first error', 'second error'], - domainValidationResult: { - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - AddDomainLogic.actions.clearDomainFormInputValue(); - }); - - it('should clear the input value', () => { - expect(AddDomainLogic.values.addDomainFormInputValue).toEqual('https://'); - }); - - it('should clear the entry point value', () => { - expect(AddDomainLogic.values.entryPointValue).toEqual('/'); - }); - - it('should reset validation completion', () => { - expect(AddDomainLogic.values.hasValidationCompleted).toEqual(false); - }); - - it('should clear errors', () => { - expect(AddDomainLogic.values.errors).toEqual([]); - }); - - it('should clear validation results', () => { - expect(AddDomainLogic.values.domainValidationResult).toEqual({ - steps: { - contentVerification: { state: '' }, - indexingRestrictions: { state: '' }, - initialValidation: { state: '' }, - networkConnectivity: { state: '' }, - }, - }); - }); - }); - - describe('onSubmitNewDomainError', () => { - it('should set errors', () => { - mount(); - - AddDomainLogic.actions.onSubmitNewDomainError(['first error', 'second error']); - - expect(AddDomainLogic.values.errors).toEqual(['first error', 'second error']); - }); - }); - - describe('setAddDomainFormInputValue', () => { - beforeEach(() => { - mount({ - addDomainFormInputValue: 'https://elastic.co', - entryPointValue: '/customers', - hasValidationCompleted: true, - errors: ['first error', 'second error'], - domainValidationResult: { - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - AddDomainLogic.actions.setAddDomainFormInputValue('https://swiftype.com/site-search'); - }); - - it('should set the input value', () => { - expect(AddDomainLogic.values.addDomainFormInputValue).toEqual( - 'https://swiftype.com/site-search' - ); - }); - - it('should clear the entry point value', () => { - expect(AddDomainLogic.values.entryPointValue).toEqual('/'); - }); - - it('should reset validation completion', () => { - expect(AddDomainLogic.values.hasValidationCompleted).toEqual(false); - }); - - it('should clear errors', () => { - expect(AddDomainLogic.values.errors).toEqual([]); - }); - - it('should clear validation results', () => { - expect(AddDomainLogic.values.domainValidationResult).toEqual({ - steps: { - contentVerification: { state: '' }, - indexingRestrictions: { state: '' }, - initialValidation: { state: '' }, - networkConnectivity: { state: '' }, - }, - }); - }); - }); - - describe('setDomainValidationResult', () => { - it('should update the validation result', () => { - AddDomainLogic.actions.setDomainValidationResult({ - contentVerification: { state: 'invalid' }, - }); - - expect(AddDomainLogic.values.domainValidationResult).toEqual({ - steps: { - contentVerification: { state: 'invalid' }, - indexingRestrictions: { state: '' }, - initialValidation: { state: '' }, - networkConnectivity: { state: '' }, - }, - }); - }); - }); - - describe('setIgnoreValidationFailure', () => { - beforeEach(() => { - mount({ - addDomainFormInputValue: 'https://elastic.co', - entryPointValue: '/customers', - hasValidationCompleted: true, - errors: ['first error', 'second error'], - domainValidationResult: { - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - AddDomainLogic.actions.setIgnoreValidationFailure(true); - }); - - it('should set the input value', () => { - expect(AddDomainLogic.values.ignoreValidationFailure).toEqual(true); - }); - }); - - describe('submitNewDomain', () => { - it('should clear errors', () => { - mount({ - errors: ['first-error', 'second error'], - }); - - AddDomainLogic.actions.submitNewDomain(); - - expect(AddDomainLogic.values.errors).toEqual([]); - }); - }); - - describe('validateDomainInitialVerification', () => { - beforeEach(() => { - mount({ - addDomainFormInputValue: 'https://elastic.co', - entryPointValue: '/customers', - hasValidationCompleted: true, - errors: ['first error', 'second error'], - }); - - AddDomainLogic.actions.validateDomainInitialVerification( - 'https://swiftype.com', - '/site-search' - ); - }); - - it('should set the input value', () => { - expect(AddDomainLogic.values.addDomainFormInputValue).toEqual('https://swiftype.com'); - }); - - it('should set the entry point value', () => { - expect(AddDomainLogic.values.entryPointValue).toEqual('/site-search'); - }); - - it('should clear errors', () => { - expect(AddDomainLogic.values.errors).toEqual([]); - }); - }); - - describe('startDomainValidation', () => { - it('should set validation results to loading', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: '' }, - indexingRestrictions: { state: '' }, - initialValidation: { state: '' }, - networkConnectivity: { state: '' }, - }, - }, - }); - - AddDomainLogic.actions.startDomainValidation(); - - expect(AddDomainLogic.values.domainValidationResult).toEqual({ - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('onSubmitNewDomainSuccess', () => { - it('should flash a success toast', () => { - const { navigateToUrl } = mockKibanaValues; - - AddDomainLogic.actions.onSubmitNewDomainSuccess({ id: 'test-domain' } as CrawlerDomain); - - expect(flashSuccessToast).toHaveBeenCalled(); - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/crawler/domains/test-domain' - ); - }); - }); - - describe('submitNewDomain', () => { - it('calls the domains endpoint with a JSON formatted body', async () => { - mount({ - addDomainFormInputValue: 'https://elastic.co', - entryPointValue: '/guide', - }); - http.post.mockReturnValueOnce(Promise.resolve({})); - - AddDomainLogic.actions.submitNewDomain(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/domains', - { - query: { - respond_with: 'crawler_details', - }, - body: JSON.stringify({ - name: 'https://elastic.co', - entry_points: [{ value: '/guide' }], - }), - } - ); - }); - - describe('on success', () => { - it('sets crawler data', async () => { - http.post.mockReturnValueOnce( - Promise.resolve({ - domains: [], - events: [], - most_recent_crawl_request: null, - }) - ); - - AddDomainLogic.actions.submitNewDomain(); - await nextTick(); - - expect(CrawlerLogic.actions.onReceiveCrawlerData).toHaveBeenCalledWith({ - domains: [], - events: [], - mostRecentCrawlRequest: null, - }); - }); - - it('calls the success callback with the most recent domain', async () => { - http.post.mockReturnValueOnce( - Promise.resolve({ - domains: [ - { - id: '1', - name: 'https://elastic.co/guide', - }, - { - id: '2', - name: 'https://swiftype.co/site-search', - }, - ], - events: [], - most_recent_crawl_request: null, - }) - ); - jest.spyOn(AddDomainLogic.actions, 'onSubmitNewDomainSuccess'); - AddDomainLogic.actions.submitNewDomain(); - await nextTick(); - - expect(AddDomainLogic.actions.onSubmitNewDomainSuccess).toHaveBeenCalledWith({ - id: '2', - url: 'https://swiftype.co/site-search', - }); - }); - }); - - describe('on error', () => { - it('passes error messages to the error callback', async () => { - jest.spyOn(AddDomainLogic.actions, 'onSubmitNewDomainError'); - - http.post.mockReturnValueOnce( - Promise.reject({ - body: { - attributes: { - errors: ['first error', 'second error'], - }, - }, - }) - ); - - AddDomainLogic.actions.submitNewDomain(); - await nextTick(); - - expect(AddDomainLogic.actions.onSubmitNewDomainError).toHaveBeenCalledWith([ - 'first error', - 'second error', - ]); - }); - }); - }); - - describe('startDomainValidation', () => { - it('extracts the domain and entrypoint and passes them to the callback ', async () => { - mount({ addDomainFormInputValue: 'https://swiftype.com/site-search' }); - jest.spyOn(AddDomainLogic.actions, 'validateDomainInitialVerification'); - - AddDomainLogic.actions.startDomainValidation(); - await nextTick(); - - expect(AddDomainLogic.actions.validateDomainInitialVerification).toHaveBeenCalledWith( - 'https://swiftype.com', - '/site-search' - ); - expect(getDomainWithProtocol).toHaveBeenCalledWith('https://swiftype.com'); - }); - }); - - describe('validateDomainInitialVerification', () => { - it('validates the url', async () => { - jest.spyOn(AddDomainLogic.actions, 'performDomainValidationStep'); - - AddDomainLogic.actions.validateDomainInitialVerification('https://elastic.co', '/'); - await nextTick(); - - expect(AddDomainLogic.actions.performDomainValidationStep).toHaveBeenCalledWith( - 'initialValidation', - ['url'] - ); - }); - }); - - describe('validateDomainContentVerification', () => { - it('validates the domain content', async () => { - jest.spyOn(AddDomainLogic.actions, 'performDomainValidationStep'); - - AddDomainLogic.actions.validateDomainContentVerification(); - await nextTick(); - - expect(AddDomainLogic.actions.performDomainValidationStep).toHaveBeenCalledWith( - 'contentVerification', - ['url_request', 'url_content'] - ); - }); - }); - - describe('validateDomainIndexingRestrictions', () => { - it("validates the domain's robots.txt", async () => { - jest.spyOn(AddDomainLogic.actions, 'performDomainValidationStep'); - - AddDomainLogic.actions.validateDomainIndexingRestrictions(); - await nextTick(); - - expect(AddDomainLogic.actions.performDomainValidationStep).toHaveBeenCalledWith( - 'indexingRestrictions', - ['robots_txt'] - ); - }); - }); - - describe('validateDomainNetworkConnectivity', () => { - it("validates the domain's dns", async () => { - jest.spyOn(AddDomainLogic.actions, 'performDomainValidationStep'); - - AddDomainLogic.actions.validateDomainNetworkConnectivity(); - await nextTick(); - - expect(AddDomainLogic.actions.performDomainValidationStep).toHaveBeenCalledWith( - 'networkConnectivity', - ['dns', 'tcp'] - ); - }); - }); - - describe('performDomainValidationStep', () => { - beforeEach(() => { - mount({ - addDomainFormInputValue: 'https://elastic.co', - }); - }); - - describe('on success', () => { - it('sets all remaining validation steps invalid on a blocking failure', async () => { - http.post.mockReturnValueOnce( - Promise.resolve({ - valid: false, - results: [ - { - name: '-', - result: 'failure', - comment: 'Something unexpected happened', - }, - ], - }) - ); - - jest.spyOn(AddDomainLogic.actions, 'setDomainValidationResult'); - - AddDomainLogic.actions.performDomainValidationStep('initialValidation', ['url']); - await nextTick(); - - expect(AddDomainLogic.actions.setDomainValidationResult).toHaveBeenCalledWith({ - initialValidation: { - state: 'invalid', - blockingFailure: true, - message: 'Something unexpected happened', - }, - networkConnectivity: { - state: 'invalid', - message: - 'Unable to establish a network connection because the "Initial validation" check failed.', - }, - indexingRestrictions: { - state: 'invalid', - message: - 'Unable to determine indexing restrictions because the "Network connectivity" check failed.', - }, - contentVerification: { - state: 'invalid', - message: 'Unable to verify content because the "Indexing restrictions" check failed.', - }, - }); - }); - - describe('when there are no blocking failures', () => { - beforeEach(() => { - http.post.mockReturnValue( - Promise.resolve({ - valid: true, - results: [], - }) - ); - }); - - it('updates the validation result', async () => { - jest.spyOn(AddDomainLogic.actions, 'setDomainValidationResult'); - - AddDomainLogic.actions.performDomainValidationStep('initialValidation', ['url']); - await nextTick(); - - expect(AddDomainLogic.actions.setDomainValidationResult).toHaveBeenCalledWith({ - initialValidation: { - state: 'valid', - }, - }); - }); - - describe('validation chain', () => { - beforeEach(() => { - http.post.mockReturnValue( - Promise.resolve({ - valid: true, - results: [], - }) - ); - }); - - it('checks network connectivity after initial validation', async () => { - jest.spyOn(AddDomainLogic.actions, 'validateDomainNetworkConnectivity'); - - AddDomainLogic.actions.performDomainValidationStep('initialValidation', ['url']); - await nextTick(); - - expect(AddDomainLogic.actions.validateDomainNetworkConnectivity).toHaveBeenCalled(); - }); - - it('checks indexing restrictions after network connectivity', async () => { - jest.spyOn(AddDomainLogic.actions, 'validateDomainIndexingRestrictions'); - - AddDomainLogic.actions.performDomainValidationStep('networkConnectivity', ['url']); - await nextTick(); - - expect(AddDomainLogic.actions.validateDomainIndexingRestrictions).toHaveBeenCalled(); - }); - - it('checks content after indexing restrictions', async () => { - jest.spyOn(AddDomainLogic.actions, 'validateDomainContentVerification'); - - AddDomainLogic.actions.performDomainValidationStep('indexingRestrictions', ['url']); - await nextTick(); - - expect(AddDomainLogic.actions.validateDomainContentVerification).toHaveBeenCalled(); - }); - }); - }); - }); - - describe('on failure', () => { - it('it sets all remaining validation steps as invalid', async () => { - http.post.mockReturnValueOnce(Promise.reject({})); - - jest.spyOn(AddDomainLogic.actions, 'setDomainValidationResult'); - - AddDomainLogic.actions.performDomainValidationStep('initialValidation', ['url']); - await nextTick(); - - expect(AddDomainLogic.actions.setDomainValidationResult).toHaveBeenCalledWith({ - initialValidation: { - state: 'invalid', - blockingFailure: true, - message: 'Unexpected error', - }, - networkConnectivity: { - state: 'invalid', - message: - 'Unable to establish a network connection because the "Initial validation" check failed.', - }, - indexingRestrictions: { - state: 'invalid', - message: - 'Unable to determine indexing restrictions because the "Network connectivity" check failed.', - }, - contentVerification: { - state: 'invalid', - message: 'Unable to verify content because the "Indexing restrictions" check failed.', - }, - }); - }); - }); - }); - }); - - describe('selectors', () => { - describe('isValidationLoading', () => { - it('is false by default', () => { - expect(AddDomainLogic.values.isValidationLoading).toEqual(false); - }); - - it('is true when any steps are loading', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'valid' }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'valid' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - expect(AddDomainLogic.values.isValidationLoading).toEqual(true); - }); - }); - - describe('hasValidationCompleted', () => { - it('is false when steps are loading', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - expect(AddDomainLogic.values.hasValidationCompleted).toEqual(false); - }); - - it('is true when all steps no steps are valid or invalid', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'valid' }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'invalid' }, - networkConnectivity: { state: 'invalid' }, - }, - }, - }); - - expect(AddDomainLogic.values.hasValidationCompleted).toEqual(true); - }); - }); - - describe('hasBlockingFailure', () => { - it('is true when any steps have blocking failures', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'valid' }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'valid' }, - networkConnectivity: { state: 'invalid', blockingFailure: true }, - }, - }, - }); - - expect(AddDomainLogic.values.hasBlockingFailure).toEqual(true); - }); - }); - - describe('canIgnoreValidationFailure', () => { - it('is true when any steps have blocking failures', () => { - mount({ - hasValidationCompleted: true, - domainValidationResult: { - steps: { - contentVerification: { state: 'invalid', blockingFailure: true }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'valid' }, - networkConnectivity: { state: 'valid' }, - }, - }, - }); - - expect(AddDomainLogic.values.canIgnoreValidationFailure).toEqual(true); - }); - - it('is false when validation has not completed', () => { - mount({ - hasValidationCompleted: false, - }); - - expect(AddDomainLogic.values.canIgnoreValidationFailure).toEqual(false); - }); - }); - - describe('allowSubmit', () => { - it('is true when a user has validated all steps and has no failures', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'valid' }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'valid' }, - networkConnectivity: { state: 'valid' }, - }, - }, - }); - - expect(AddDomainLogic.values.allowSubmit).toEqual(true); - }); - - it('is true when a user ignores validation failure', () => { - mount({ - ignoreValidationFailure: true, - domainValidationResult: { - steps: { - contentVerification: { state: 'valid' }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'invalid' }, - networkConnectivity: { state: 'invalid' }, - }, - }, - }); - - expect(AddDomainLogic.values.allowSubmit).toEqual(true); - }); - }); - - describe('displayValidation', () => { - it('is true when a user is loading validation', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - expect(AddDomainLogic.values.displayValidation).toEqual(true); - }); - - it('is true when a user has completed validation', () => { - mount({ - domainValidationResult: { - steps: { - contentVerification: { state: 'valid' }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'valid' }, - networkConnectivity: { state: 'invalid' }, - }, - }, - }); - - expect(AddDomainLogic.values.displayValidation).toEqual(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts deleted file mode 100644 index b11b30e237284..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.ts +++ /dev/null @@ -1,345 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { flashSuccessToast } from '../../../../../shared/flash_messages'; -import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors'; - -import { HttpLogic } from '../../../../../shared/http'; -import { KibanaLogic } from '../../../../../shared/kibana'; -import { ENGINE_CRAWLER_DOMAIN_PATH } from '../../../../routes'; -import { EngineLogic, generateEnginePath } from '../../../engine'; - -import { CrawlerLogic } from '../../crawler_logic'; -import { - CrawlerDataFromServer, - CrawlerDomain, - CrawlerDomainValidationResult, - CrawlerDomainValidationResultChange, - CrawlerDomainValidationResultFromServer, - CrawlerDomainValidationStepName, -} from '../../types'; -import { crawlDomainValidationToResult, crawlerDataServerToClient } from '../../utils'; - -import { - domainValidationFailureResultChange, - extractDomainAndEntryPointFromUrl, - getDomainWithProtocol, -} from './utils'; - -export interface AddDomainLogicValues { - addDomainFormInputValue: string; - allowSubmit: boolean; - canIgnoreValidationFailure: boolean; - domainValidationResult: CrawlerDomainValidationResult; - entryPointValue: string; - errors: string[]; - hasBlockingFailure: boolean; - hasValidationCompleted: boolean; - ignoreValidationFailure: boolean; - isValidationLoading: boolean; - displayValidation: boolean; -} - -export interface AddDomainLogicActions { - clearDomainFormInputValue(): void; - onSubmitNewDomainError(errors: string[]): { errors: string[] }; - onSubmitNewDomainSuccess(domain: CrawlerDomain): { domain: CrawlerDomain }; - performDomainValidationStep( - stepName: CrawlerDomainValidationStepName, - checks: string[] - ): { - stepName: CrawlerDomainValidationStepName; - checks: string[]; - }; - setAddDomainFormInputValue(newValue: string): string; - setDomainValidationResult(change: CrawlerDomainValidationResultChange): { - change: CrawlerDomainValidationResultChange; - }; - setIgnoreValidationFailure(newValue: boolean): boolean; - startDomainValidation(): void; - submitNewDomain(): void; - validateDomainInitialVerification( - newValue: string, - newEntryPointValue: string - ): { newValue: string; newEntryPointValue: string }; - validateDomainContentVerification(): void; - validateDomainIndexingRestrictions(): void; - validateDomainNetworkConnectivity(): void; -} - -const DEFAULT_SELECTOR_VALUES = { - addDomainFormInputValue: 'https://', - entryPointValue: '/', - domainValidationResult: { - steps: { - contentVerification: { state: '' }, - indexingRestrictions: { state: '' }, - initialValidation: { state: '' }, - networkConnectivity: { state: '' }, - }, - } as CrawlerDomainValidationResult, - allowSubmit: false, - ignoreValidationFailure: false, - isValidationLoading: false, -}; - -export const AddDomainLogic = kea>({ - path: ['enterprise_search', 'app_search', 'crawler', 'add_domain'], - actions: () => ({ - clearDomainFormInputValue: true, - initialValidation: true, - performDomainValidationStep: (stepName, checks) => ({ stepName, checks }), - onSubmitNewDomainSuccess: (domain) => ({ domain }), - onSubmitNewDomainError: (errors) => ({ errors }), - setAddDomainFormInputValue: (newValue) => newValue, - setDomainValidationResult: (change: CrawlerDomainValidationResultChange) => ({ change }), - setIgnoreValidationFailure: (newValue) => newValue, - startDomainValidation: true, - submitNewDomain: true, - validateDomainInitialVerification: (newValue, newEntryPointValue) => ({ - newValue, - newEntryPointValue, - }), - validateDomainContentVerification: true, - validateDomainIndexingRestrictions: true, - validateDomainNetworkConnectivity: true, - }), - // @ts-expect-error upgrade typescript v5.1.6 - reducers: () => ({ - addDomainFormInputValue: [ - DEFAULT_SELECTOR_VALUES.addDomainFormInputValue, - { - clearDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.addDomainFormInputValue, - // @ts-expect-error upgrade typescript v5.1.6 - setAddDomainFormInputValue: (_, newValue: string) => newValue, - // @ts-expect-error upgrade typescript v5.1.6 - validateDomainInitialVerification: (_, { newValue }: { newValue: string }) => newValue, - }, - ], - domainValidationResult: [ - DEFAULT_SELECTOR_VALUES.domainValidationResult, - { - clearDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.domainValidationResult, - setAddDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.domainValidationResult, - // @ts-expect-error upgrade typescript v5.1.6 - setDomainValidationResult: ({ steps }, { change }) => ({ - steps: { - ...steps, - ...change, - }, - }), - startDomainValidation: () => ({ - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }), - }, - ], - entryPointValue: [ - DEFAULT_SELECTOR_VALUES.entryPointValue, - { - clearDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.entryPointValue, - setAddDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.entryPointValue, - // @ts-expect-error upgrade typescript v5.1.6 - validateDomainInitialVerification: (_, { newEntryPointValue }) => newEntryPointValue, - }, - ], - errors: [ - [], - { - clearDomainFormInputValue: () => [], - setAddDomainFormInputValue: () => [], - validateDomainInitialVerification: () => [], - submitNewDomain: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSubmitNewDomainError: (_, { errors }) => errors, - }, - ], - ignoreValidationFailure: [ - DEFAULT_SELECTOR_VALUES.ignoreValidationFailure, - { - clearDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.ignoreValidationFailure, - setAddDomainFormInputValue: () => DEFAULT_SELECTOR_VALUES.ignoreValidationFailure, - // @ts-expect-error upgrade typescript v5.1.6 - setIgnoreValidationFailure: (_, newValue: boolean) => newValue, - }, - ], - }), - selectors: ({ selectors }) => ({ - isValidationLoading: [ - () => [selectors.domainValidationResult], - (domainValidationResult: CrawlerDomainValidationResult) => - !!Object.values(domainValidationResult.steps).find((step) => step.state === 'loading'), - ], - hasValidationCompleted: [ - () => [selectors.domainValidationResult], - (domainValidationResult: CrawlerDomainValidationResult) => - !Object.values(domainValidationResult.steps).find( - (step) => step.state === 'loading' || step.state === '' - ), - ], - hasBlockingFailure: [ - () => [selectors.domainValidationResult], - (domainValidationResult: CrawlerDomainValidationResult) => - !!Object.values(domainValidationResult.steps).find((step) => step.blockingFailure), - ], - canIgnoreValidationFailure: [ - () => [selectors.hasValidationCompleted, selectors.domainValidationResult], - (hasValidationCompleted: boolean, domainValidationResult: CrawlerDomainValidationResult) => { - if (!hasValidationCompleted) { - return false; - } - - return ( - domainValidationResult.steps.indexingRestrictions.blockingFailure || - domainValidationResult.steps.contentVerification.blockingFailure - ); - }, - ], - allowSubmit: [ - () => [ - selectors.ignoreValidationFailure, - selectors.hasValidationCompleted, - selectors.hasBlockingFailure, - ], - (ignoreValidationFailure, hasValidationCompleted, hasBlockingFailure) => { - if (ignoreValidationFailure) { - return true; - } - - return hasValidationCompleted && !hasBlockingFailure; - }, - ], - displayValidation: [ - () => [selectors.isValidationLoading, selectors.hasValidationCompleted], - (isValidationLoading, hasValidationCompleted) => - isValidationLoading || hasValidationCompleted, - ], - }), - listeners: ({ actions, values }) => ({ - onSubmitNewDomainSuccess: ({ domain }) => { - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.add.successMessage', - { - defaultMessage: "Successfully added domain ''{domainUrl}''", - values: { - domainUrl: domain.url, - }, - } - ) - ); - KibanaLogic.values.navigateToUrl( - generateEnginePath(ENGINE_CRAWLER_DOMAIN_PATH, { domainId: domain.id }) - ); - }, - performDomainValidationStep: async ({ stepName, checks }) => { - const { http } = HttpLogic.values; - const failureResultChange = domainValidationFailureResultChange(stepName); - - const route = '/internal/app_search/crawler/validate_url'; - - try { - const data = await http.post(route, { - body: JSON.stringify({ url: values.addDomainFormInputValue.trim(), checks }), - }); - const result = crawlDomainValidationToResult(data); - - if (result.blockingFailure) { - actions.setDomainValidationResult({ [stepName]: result, ...failureResultChange }); - } else { - actions.setDomainValidationResult({ [stepName]: result }); - - // Trigger next step - switch (stepName) { - case 'initialValidation': - actions.validateDomainNetworkConnectivity(); - break; - case 'networkConnectivity': - actions.validateDomainIndexingRestrictions(); - break; - case 'indexingRestrictions': - actions.validateDomainContentVerification(); - break; - } - } - } catch (e) { - actions.setDomainValidationResult({ - [stepName]: { - state: 'invalid', - blockingFailure: true, - message: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.unexpectedValidationErrorMessage', - { defaultMessage: 'Unexpected error' } - ), - }, - ...failureResultChange, - }); - } - }, - submitNewDomain: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const requestBody = JSON.stringify({ - name: values.addDomainFormInputValue.trim(), - entry_points: [{ value: values.entryPointValue }], - }); - - try { - const response = await http.post( - `/internal/app_search/engines/${engineName}/crawler/domains`, - { - query: { - respond_with: 'crawler_details', - }, - body: requestBody, - } - ); - - const crawlerData = crawlerDataServerToClient(response as CrawlerDataFromServer); - CrawlerLogic.actions.onReceiveCrawlerData(crawlerData); - const newDomain = crawlerData.domains[crawlerData.domains.length - 1]; - if (newDomain) { - actions.onSubmitNewDomainSuccess(newDomain); - } - // If there is not a new domain, that means the server responded with a 200 but - // didn't actually persist the new domain to our BE, and we take no action - } catch (e) { - // we surface errors inside the form instead of in flash messages - const errorMessages = getErrorsFromHttpResponse(e); - actions.onSubmitNewDomainError(errorMessages); - } - }, - startDomainValidation: async () => { - const { domain, entryPoint } = extractDomainAndEntryPointFromUrl( - values.addDomainFormInputValue.trim() - ); - const domainWithProtocol = await getDomainWithProtocol(domain); - actions.validateDomainInitialVerification(domainWithProtocol, entryPoint); - }, - validateDomainInitialVerification: () => { - actions.performDomainValidationStep('initialValidation', ['url']); - }, - validateDomainContentVerification: () => { - actions.performDomainValidationStep('contentVerification', ['url_request', 'url_content']); - }, - validateDomainIndexingRestrictions: () => { - actions.performDomainValidationStep('indexingRestrictions', ['robots_txt']); - }, - validateDomainNetworkConnectivity: () => { - actions.performDomainValidationStep('networkConnectivity', ['dns', 'tcp']); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_validation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_validation.test.tsx deleted file mode 100644 index ded46faf59f2c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_validation.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCheckbox } from '@elastic/eui'; - -import { AddDomainValidation } from './add_domain_validation'; -import { ValidationStepPanel } from './validation_step_panel'; - -describe('AddDomainValidation', () => { - const actions = { - setIgnoreValidationFailure: jest.fn(), - }; - - it('contains four validation steps', () => { - setMockValues({ - addDomainFormInputValue: 'https://elastic.co', - domainValidationResult: { - steps: { - contentVerification: { state: 'loading' }, - indexingRestrictions: { state: 'loading' }, - initialValidation: { state: 'loading' }, - networkConnectivity: { state: 'loading' }, - }, - }, - }); - - const wrapper = shallow(); - - expect(wrapper.find(ValidationStepPanel)).toHaveLength(4); - }); - - it('can ignore validation failure', () => { - setMockValues({ - canIgnoreValidationFailure: true, - ignoreValidationFailure: false, - addDomainFormInputValue: 'https://elastic.co', - domainValidationResult: { - steps: { - contentVerification: { state: 'invalid', blockingFailure: true }, - indexingRestrictions: { state: 'valid' }, - initialValidation: { state: 'valid' }, - networkConnectivity: { state: 'valid' }, - }, - }, - }); - setMockActions(actions); - - const wrapper = shallow(); - wrapper - .find(EuiCheckbox) - .first() - .simulate('change', { target: { checked: true } }); - - expect(actions.setIgnoreValidationFailure).toHaveBeenCalledWith(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_validation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_validation.tsx deleted file mode 100644 index 817c43df4f8b6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_validation.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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, - EuiCheckbox, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { AddDomainLogic } from './add_domain_logic'; -import { ValidationStepPanel } from './validation_step_panel'; - -export const AddDomainValidation: React.FC = () => { - const { - addDomainFormInputValue, - canIgnoreValidationFailure, - domainValidationResult, - ignoreValidationFailure, - } = useValues(AddDomainLogic); - const { setIgnoreValidationFailure } = useActions(AddDomainLogic); - - return ( - <> - - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.testUrlButtonLabel', - { - defaultMessage: 'Test URL in the browser', - } - )} - - } - /> - - - - - - - - {canIgnoreValidationFailure && ( - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationTitle', - { - defaultMessage: 'Ignore validation failures and continue', - } - )} - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.ignoreValidationDescription', - { - defaultMessage: - 'The web crawler will be unable to index any content on this domain until the errors above are addressed.', - } - )} - - - } - checked={ignoreValidationFailure} - onChange={(e) => setIgnoreValidationFailure(e.target.checked)} - /> - - - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.test.ts deleted file mode 100644 index 31eb657374a62..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 { - domainValidationFailureResultChange, - domainValidationStateToPanelColor, - extractDomainAndEntryPointFromUrl, - getDomainWithProtocol, -} from './utils'; - -describe('extractDomainAndEntryPointFromUrl', () => { - it('extracts a provided entry point and domain', () => { - expect(extractDomainAndEntryPointFromUrl('https://elastic.co/guide')).toEqual({ - domain: 'https://elastic.co', - entryPoint: '/guide', - }); - }); - - it('provides a default entry point if there is only a domain', () => { - expect(extractDomainAndEntryPointFromUrl('https://elastic.co')).toEqual({ - domain: 'https://elastic.co', - entryPoint: '/', - }); - }); -}); - -describe('getDomainWithProtocol', () => { - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('passes through domain if it starts with https', async () => { - const result = await getDomainWithProtocol('https://elastic.co'); - - expect(result).toEqual('https://elastic.co'); - expect(http.post).toHaveBeenCalledTimes(0); - }); - - it('passes through domain if it starts with http', async () => { - const result = await getDomainWithProtocol('http://elastic.co'); - - expect(result).toEqual('http://elastic.co'); - expect(http.post).toHaveBeenCalledTimes(0); - }); - - it('returns domain with https protocol if the back-end validates https', async () => { - http.post.mockReturnValueOnce(Promise.resolve({ valid: true })); - const result = await getDomainWithProtocol('elastic.co'); - - expect(result).toEqual('https://elastic.co'); - expect(http.post).toHaveBeenCalledTimes(1); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/crawler/validate_url', { - body: JSON.stringify({ url: 'https://elastic.co', checks: ['tcp', 'url_request'] }), - }); - }); - - it('returns domain with http protocol if the back-end validates http', async () => { - http.post - .mockReturnValueOnce(Promise.resolve({ valid: false })) - .mockReturnValueOnce(Promise.resolve({ valid: true })); - const result = await getDomainWithProtocol('elastic.co'); - - expect(result).toEqual('http://elastic.co'); - expect(http.post).toHaveBeenCalledTimes(2); - expect(http.post).toHaveBeenLastCalledWith('/internal/app_search/crawler/validate_url', { - body: JSON.stringify({ url: 'http://elastic.co', checks: ['tcp', 'url_request'] }), - }); - }); - - it('passes through domain if back-end throws error', async () => { - http.post.mockReturnValueOnce(Promise.reject()); - - const result = await getDomainWithProtocol('elastic.co'); - - expect(result).toEqual('elastic.co'); - expect(http.post).toHaveBeenCalledTimes(1); - }); - - it('passes through domain if back-end fails to validate https and http', async () => { - http.post.mockReturnValueOnce(Promise.resolve({ valid: false })); - http.post.mockReturnValueOnce(Promise.resolve({ valid: false })); - const result = await getDomainWithProtocol('elastic.co'); - - expect(result).toEqual('elastic.co'); - expect(http.post).toHaveBeenCalledTimes(2); - }); -}); - -describe('domainValidationStateToPanelColor', () => { - it('returns expected values', () => { - expect(domainValidationStateToPanelColor('valid')).toEqual('success'); - expect(domainValidationStateToPanelColor('invalid')).toEqual('danger'); - expect(domainValidationStateToPanelColor('')).toEqual('subdued'); - expect(domainValidationStateToPanelColor('loading')).toEqual('subdued'); - }); -}); - -describe('domainValidationFailureResultChange', () => { - it('returns the expected results', () => { - expect(domainValidationFailureResultChange('initialValidation')).toMatchObject({ - networkConnectivity: expect.any(Object), - indexingRestrictions: expect.any(Object), - contentVerification: expect.any(Object), - }); - - expect(domainValidationFailureResultChange('networkConnectivity')).toMatchObject({ - indexingRestrictions: expect.any(Object), - contentVerification: expect.any(Object), - }); - - expect(domainValidationFailureResultChange('indexingRestrictions')).toMatchObject({ - contentVerification: expect.any(Object), - }); - - expect(domainValidationFailureResultChange('contentVerification')).toEqual({}); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts deleted file mode 100644 index 1f744d5eabf9b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/utils.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { HttpLogic } from '../../../../../shared/http'; - -import { - CrawlerDomainValidationResultChange, - CrawlerDomainValidationResultFromServer, - CrawlerDomainValidationStepName, - CrawlerDomainValidationStepState, -} from '../../types'; - -export const extractDomainAndEntryPointFromUrl = ( - url: string -): { domain: string; entryPoint: string } => { - let domain = url; - let entryPoint = '/'; - - const pathSlashIndex = url.search(/[^\:\/]\//); - if (pathSlashIndex !== -1) { - domain = url.substring(0, pathSlashIndex + 1); - entryPoint = url.substring(pathSlashIndex + 1); - } - - return { domain, entryPoint }; -}; - -export const getDomainWithProtocol = async (domain: string) => { - const { http } = HttpLogic.values; - - if (!domain.startsWith('https://') && !domain.startsWith('http://')) { - try { - const route = '/internal/app_search/crawler/validate_url'; - const checks = ['tcp', 'url_request']; - - const httpsCheckData: CrawlerDomainValidationResultFromServer = await http.post(route, { - body: JSON.stringify({ url: `https://${domain}`, checks }), - }); - if (httpsCheckData.valid) { - return `https://${domain}`; - } - - const httpCheckData: CrawlerDomainValidationResultFromServer = await http.post(route, { - body: JSON.stringify({ url: `http://${domain}`, checks }), - }); - if (httpCheckData.valid) { - return `http://${domain}`; - } - } catch (error) { - // Do nothing as later validation steps will catch errors - } - } - - return domain; -}; - -export const domainValidationStateToPanelColor = ( - state: CrawlerDomainValidationStepState -): 'success' | 'warning' | 'danger' | 'subdued' => { - switch (state) { - case 'valid': - return 'success'; - case 'warning': - return 'warning'; - case 'invalid': - return 'danger'; - default: - return 'subdued'; - } -}; - -const allFailureResultChanges: CrawlerDomainValidationResultChange = { - networkConnectivity: { - state: 'invalid', - message: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.networkConnectivityFailureMessage', - { - defaultMessage: - 'Unable to establish a network connection because the "Initial validation" check failed.', - } - ), - }, - indexingRestrictions: { - state: 'invalid', - message: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.indexingRestrictionsFailureMessage', - { - defaultMessage: - 'Unable to determine indexing restrictions because the "Network connectivity" check failed.', - } - ), - }, - contentVerification: { - state: 'invalid', - message: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.addDomainForm.contentVerificationFailureMessage', - { - defaultMessage: - 'Unable to verify content because the "Indexing restrictions" check failed.', - } - ), - }, -}; - -export const domainValidationFailureResultChange = ( - stepName: CrawlerDomainValidationStepName -): CrawlerDomainValidationResultChange => { - switch (stepName) { - case 'initialValidation': - return allFailureResultChanges; - case 'networkConnectivity': - return { - indexingRestrictions: allFailureResultChanges.indexingRestrictions, - contentVerification: allFailureResultChanges.contentVerification, - }; - case 'indexingRestrictions': - return { - contentVerification: allFailureResultChanges.contentVerification, - }; - default: - return {}; - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx deleted file mode 100644 index 8bb82f93e3ec4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; - -import { ValidationStateIcon } from './validation_state_icon'; - -describe('ValidationStateIcon', () => { - it('shows a success icon when valid', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiIcon).prop('color')).toEqual('success'); - }); - - it('shows a warning icon when warning', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiIcon).prop('color')).toEqual('warning'); - }); - - it('shows a danger icon when invalid', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiIcon).prop('color')).toEqual('danger'); - }); - - it('shows a loading spinner by default', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx deleted file mode 100644 index 30d20d246722f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_state_icon.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; - -import { CrawlerDomainValidationStepState } from '../../types'; - -export const ValidationStateIcon: React.FC<{ state: CrawlerDomainValidationStepState }> = ({ - state, -}) => { - switch (state) { - case 'valid': - return ; - case 'warning': - return ; - case 'invalid': - return ; - default: - return ; - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx deleted file mode 100644 index ace6b85210fc2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { EuiPanel } from '@elastic/eui'; - -import { ValidationStateIcon } from './validation_state_icon'; -import { ValidationStepPanel } from './validation_step_panel'; - -describe('ValidationStepPanel', () => { - describe('renders', () => { - const wrapper = shallow( - - ); - - it('passed the correct color to the EuiPanel', () => { - expect(wrapper.find(EuiPanel).prop('color')).toEqual('success'); - }); - - it('contains a validation state icon', () => { - expect(wrapper.find(ValidationStateIcon)).toHaveLength(1); - }); - - it('renders a label', () => { - expect(wrapper.find('h3').text()).toEqual('Initial validation'); - }); - }); - describe('invalid messages and actions', () => { - const errorMessage = 'Error message'; - const action =
; - - it('displays the passed error message and action is invalid', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('[data-test-subj="errorMessage"]').childAt(0).text()).toContain( - 'Error message' - ); - expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(1); - }); - - it('displays the passed error message and action when state is warning', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('[data-test-subj="errorMessage"]').childAt(0).text()).toContain( - 'Error message' - ); - expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(1); - }); - - it('does not display the passed error message or action when state is loading', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('[data-test-subj="errorMessage"]')).toHaveLength(0); - expect(wrapper.find('[data-test-subj="action"]')).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx deleted file mode 100644 index 6e93fc1f74bb1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/validation_step_panel.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 { - EuiFlexGroup, - EuiFlexItem, - EuiMarkdownFormat, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; - -import { CrawlerDomainValidationStep } from '../../types'; - -import { domainValidationStateToPanelColor } from './utils'; -import { ValidationStateIcon } from './validation_state_icon'; - -interface ValidationStepPanelProps { - step: CrawlerDomainValidationStep; - label: string; - action?: React.ReactNode; -} - -export const ValidationStepPanel: React.FC = ({ - step, - label, - action, -}) => { - const showErrorMessage = step.state === 'invalid' || step.state === 'warning'; - - return ( - - - - - - - -

{label}

-
-
-
- {showErrorMessage && ( - <> - - - {step.message || ''} - - {action && ( - <> - - {action} - - )} - - )} -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout.test.tsx deleted file mode 100644 index 5ca0ea23c15b0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton, EuiButtonEmpty, EuiFlyout, EuiFlyoutFooter } from '@elastic/eui'; - -import { Loading } from '../../../../../shared/loading'; -import { rerender } from '../../../../../test_helpers'; - -import { CrawlCustomSettingsFlyout } from './crawl_custom_settings_flyout'; -import { CrawlCustomSettingsFlyoutCrawlDepthPanel } from './crawl_custom_settings_flyout_crawl_depth_panel'; -import { CrawlCustomSettingsFlyoutDomainsPanel } from './crawl_custom_settings_flyout_domains_panel'; -import { CrawlCustomSettingsFlyoutSeedUrlsPanel } from './crawl_custom_settings_flyout_seed_urls_panel'; - -const MOCK_VALUES = { - // CrawlCustomSettingsFlyoutLogic - isDataLoading: false, - isFormSubmitting: false, - isFlyoutVisible: true, - selectedDomainUrls: ['https://www.elastic.co'], -}; - -const MOCK_ACTIONS = { - // CrawlCustomSettingsFlyoutLogic - hideFlyout: jest.fn(), - onSelectDomainUrls: jest.fn(), - startCustomCrawl: jest.fn(), -}; - -describe('CrawlCustomSettingsFlyout', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - - wrapper = shallow(); - }); - - it('is empty when the flyout is hidden', () => { - setMockValues({ - ...MOCK_VALUES, - isFlyoutVisible: false, - }); - - rerender(wrapper); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('renders as a modal when visible', () => { - expect(wrapper.is(EuiFlyout)).toBe(true); - }); - - it('can be closed', () => { - expect(wrapper.prop('onClose')).toEqual(MOCK_ACTIONS.hideFlyout); - expect(wrapper.find(EuiFlyoutFooter).find(EuiButtonEmpty).prop('onClick')).toEqual( - MOCK_ACTIONS.hideFlyout - ); - }); - - it('lets the user customize their crawl', () => { - expect(wrapper.find(Loading)).toHaveLength(0); - for (const component of [ - CrawlCustomSettingsFlyoutCrawlDepthPanel, - CrawlCustomSettingsFlyoutDomainsPanel, - CrawlCustomSettingsFlyoutSeedUrlsPanel, - ]) { - expect(wrapper.find(component)).toHaveLength(1); - } - }); - - it('shows a loading state', () => { - setMockValues({ - ...MOCK_VALUES, - isDataLoading: true, - }); - - rerender(wrapper); - - expect(wrapper.find(Loading)).toHaveLength(1); - for (const component of [ - CrawlCustomSettingsFlyoutCrawlDepthPanel, - CrawlCustomSettingsFlyoutDomainsPanel, - CrawlCustomSettingsFlyoutSeedUrlsPanel, - ]) { - expect(wrapper.find(component)).toHaveLength(0); - } - }); - - describe('submit button', () => { - it('is enabled by default', () => { - setMockValues({ - ...MOCK_VALUES, - selectedDomainUrls: [], - }); - - rerender(wrapper); - - expect(wrapper.find(EuiFlyoutFooter).find(EuiButton).prop('disabled')).toEqual(true); - }); - - it('is disabled when no domains are selected', () => { - setMockValues({ - ...MOCK_VALUES, - selectedDomainUrls: [], - }); - - rerender(wrapper); - - expect(wrapper.find(EuiFlyoutFooter).find(EuiButton).prop('disabled')).toEqual(true); - }); - - it('is disabled when data is loading', () => { - setMockValues({ - ...MOCK_VALUES, - isDataLoading: true, - }); - - rerender(wrapper); - - expect(wrapper.find(EuiFlyoutFooter).find(EuiButton).prop('disabled')).toEqual(true); - }); - - it('shows a loading state when the user makes a request', () => { - setMockValues({ - ...MOCK_VALUES, - isFormSubmitting: true, - }); - - rerender(wrapper); - - expect(wrapper.find(EuiFlyoutFooter).find(EuiButton).prop('isLoading')).toEqual(true); - }); - - it('starts a crawl and hides the modal', () => { - wrapper.find(EuiFlyoutFooter).find(EuiButton).simulate('click'); - - expect(MOCK_ACTIONS.startCustomCrawl).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout.tsx deleted file mode 100644 index a310c76072284..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; -import { Loading } from '../../../../../shared/loading'; - -import { CrawlCustomSettingsFlyoutCrawlDepthPanel } from './crawl_custom_settings_flyout_crawl_depth_panel'; -import { CrawlCustomSettingsFlyoutDomainsPanel } from './crawl_custom_settings_flyout_domains_panel'; -import { CrawlCustomSettingsFlyoutLogic } from './crawl_custom_settings_flyout_logic'; -import { CrawlCustomSettingsFlyoutSeedUrlsPanel } from './crawl_custom_settings_flyout_seed_urls_panel'; - -export const CrawlCustomSettingsFlyout: React.FC = () => { - const { isDataLoading, isFormSubmitting, isFlyoutVisible, selectedDomainUrls } = useValues( - CrawlCustomSettingsFlyoutLogic - ); - const { hideFlyout, startCustomCrawl } = useActions(CrawlCustomSettingsFlyoutLogic); - - if (!isFlyoutVisible) { - return null; - } - - return ( - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeadTitle', - { - defaultMessage: 'Custom crawl configuration', - } - )} -

-
- - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.flyoutHeaderDescription', - { - defaultMessage: 'Set up a one-time crawl with custom settings.', - } - )} -

-
-
- - {isDataLoading ? ( - - ) : ( - <> - - - - - - - )} - - - - - {CANCEL_BUTTON_LABEL} - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.startCrawlButtonLabel', - { - defaultMessage: 'Apply and crawl now', - } - )} - - - - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_crawl_depth_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_crawl_depth_panel.test.tsx deleted file mode 100644 index 24932de7cfb36..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_crawl_depth_panel.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFieldNumber } from '@elastic/eui'; - -import { CrawlCustomSettingsFlyoutCrawlDepthPanel } from './crawl_custom_settings_flyout_crawl_depth_panel'; - -const MOCK_VALUES = { - // CrawlCustomSettingsFlyoutLogic - maxCrawlDepth: 5, -}; - -const MOCK_ACTIONS = { - // CrawlCustomSettingsFlyoutLogic - onSelectMaxCrawlDepth: jest.fn(), -}; - -describe('CrawlCustomSettingsFlyoutCrawlDepthPanel', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('allows the user to set max crawl depth', () => { - const wrapper = shallow(); - const crawlDepthField = wrapper.find(EuiFieldNumber); - - expect(crawlDepthField.prop('value')).toEqual(5); - - crawlDepthField.simulate('change', { target: { value: '10' } }); - - expect(MOCK_ACTIONS.onSelectMaxCrawlDepth).toHaveBeenCalledWith(10); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_crawl_depth_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_crawl_depth_panel.tsx deleted file mode 100644 index f52f0dacf4f2f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_crawl_depth_panel.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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, { ChangeEvent } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { - EuiFieldNumber, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiPanel, - EuiText, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CrawlCustomSettingsFlyoutLogic } from './crawl_custom_settings_flyout_logic'; - -export const CrawlCustomSettingsFlyoutCrawlDepthPanel: React.FC = () => { - const { maxCrawlDepth } = useValues(CrawlCustomSettingsFlyoutLogic); - const { onSelectMaxCrawlDepth } = useActions(CrawlCustomSettingsFlyoutLogic); - - return ( - - - - - ) => - onSelectMaxCrawlDepth(parseInt(e.target.value, 10)) - } - /> - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.maxCrawlDepthFieldDescription', - { - defaultMessage: - 'Set a max crawl depth to specify how many pages deep the crawler should traverse. Set the value to one (1) to limit the crawl to only the entry points.', - } - )} - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_domains_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_domains_panel.test.tsx deleted file mode 100644 index e0433a70a3df5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_domains_panel.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiAccordion, EuiNotificationBadge } from '@elastic/eui'; - -import { rerender } from '../../../../../test_helpers'; -import { SimplifiedSelectable } from '../crawl_select_domains_modal/simplified_selectable'; - -import { CrawlCustomSettingsFlyoutDomainsPanel } from './crawl_custom_settings_flyout_domains_panel'; - -const MOCK_VALUES = { - // CrawlCustomSettingsFlyoutLogic - domainUrls: ['https://www.elastic.co', 'https://www.swiftype.com'], - selectedDomainUrls: ['https://www.elastic.co'], -}; - -const MOCK_ACTIONS = { - // CrawlCustomSettingsFlyoutLogic - onSelectDomainUrls: jest.fn(), -}; - -const getAccordionBadge = (wrapper: ShallowWrapper) => { - const accordionWrapper = wrapper.find(EuiAccordion); - const extraActionWrapper = shallow(
{accordionWrapper.prop('extraAction')}
); - return extraActionWrapper.find(EuiNotificationBadge); -}; - -describe('CrawlCustomSettingsFlyoutDomainsPanel', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - - wrapper = shallow(); - }); - - it('allows the user to select domains', () => { - const domainAccordionWrapper = wrapper.find(EuiAccordion); - - expect(domainAccordionWrapper.find(SimplifiedSelectable).props()).toEqual({ - options: ['https://www.elastic.co', 'https://www.swiftype.com'], - selectedOptions: ['https://www.elastic.co'], - onChange: MOCK_ACTIONS.onSelectDomainUrls, - }); - }); - - it('indicates how many domains are selected', () => { - let badge = getAccordionBadge(wrapper); - - expect(badge.render().text()).toContain('1'); - expect(badge.prop('color')).toEqual('accent'); - - setMockValues({ - ...MOCK_VALUES, - selectedDomainUrls: [], - }); - - rerender(wrapper); - badge = getAccordionBadge(wrapper); - - expect(badge.render().text()).toContain('0'); - expect(badge.prop('color')).toEqual('subdued'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_domains_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_domains_panel.tsx deleted file mode 100644 index 705e75fe36e7d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_domains_panel.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiAccordion, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiNotificationBadge, - EuiPanel, - EuiTitle, - useGeneratedHtmlId, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { SimplifiedSelectable } from '../crawl_select_domains_modal/simplified_selectable'; - -import { CrawlCustomSettingsFlyoutLogic } from './crawl_custom_settings_flyout_logic'; - -export const CrawlCustomSettingsFlyoutDomainsPanel: React.FC = () => { - const { domainUrls, selectedDomainUrls } = useValues(CrawlCustomSettingsFlyoutLogic); - const { onSelectDomainUrls } = useActions(CrawlCustomSettingsFlyoutLogic); - - return ( - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.domainsAccordionButtonLabel', - { - defaultMessage: 'Add domains to your crawl', - } - )} -

-
-
- - } - extraAction={ - - 0 ? 'accent' : 'subdued'} - > - {selectedDomainUrls.length} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.selectedDescriptor', - { - defaultMessage: 'selected', - } - )} - - - } - > - -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts deleted file mode 100644 index 0238cf6e7a439..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts +++ /dev/null @@ -1,518 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; -import { CrawlerLogic } from '../../crawler_logic'; -import { DomainConfig } from '../../types'; - -import { CrawlCustomSettingsFlyoutLogic } from './crawl_custom_settings_flyout_logic'; - -describe('CrawlCustomSettingsFlyoutLogic', () => { - const { mount } = new LogicMounter(CrawlCustomSettingsFlyoutLogic); - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(CrawlCustomSettingsFlyoutLogic.values).toEqual({ - customEntryPointUrls: [], - customSitemapUrls: [], - domainConfigMap: {}, - domainConfigs: [], - domainUrls: [], - entryPointUrls: [], - includeSitemapsInRobotsTxt: true, - isDataLoading: true, - isFlyoutVisible: false, - isFormSubmitting: false, - maxCrawlDepth: 2, - selectedDomainUrls: [], - selectedEntryPointUrls: [], - selectedSitemapUrls: [], - sitemapUrls: [], - }); - }); - - describe('actions', () => { - describe('fetchDomainConfigData', () => { - it('updates logic with data that has been converted from server to client', async () => { - jest.spyOn(CrawlCustomSettingsFlyoutLogic.actions, 'onRecieveDomainConfigData'); - - http.get.mockReturnValueOnce( - Promise.resolve({ - meta: { - page: { - current: 1, - size: 1, - total_pages: 2, - }, - }, - results: [ - { - id: '1234', - name: 'https://www.elastic.co', - seed_urls: [], - sitemap_urls: [], - }, - ], - }) - ); - - http.get.mockReturnValueOnce( - Promise.resolve({ - meta: { - page: { - current: 2, - size: 1, - total_pages: 2, - }, - }, - results: [ - { - id: '5678', - name: 'https://www.swiftype.com', - seed_urls: [], - sitemap_urls: [], - }, - ], - }) - ); - - CrawlCustomSettingsFlyoutLogic.actions.fetchDomainConfigData(); - await nextTick(); - - expect(http.get).toHaveBeenNthCalledWith( - 1, - '/internal/app_search/engines/some-engine/crawler/domain_configs', - { - query: { - 'page[current]': 1, - 'page[size]': 100, - }, - } - ); - expect(http.get).toHaveBeenNthCalledWith( - 2, - '/internal/app_search/engines/some-engine/crawler/domain_configs', - { - query: { - 'page[current]': 2, - 'page[size]': 1, - }, - } - ); - - expect( - CrawlCustomSettingsFlyoutLogic.actions.onRecieveDomainConfigData - ).toHaveBeenCalledWith([ - { - id: '1234', - name: 'https://www.elastic.co', - seedUrls: [], - sitemapUrls: [], - }, - { - id: '5678', - name: 'https://www.swiftype.com', - seedUrls: [], - sitemapUrls: [], - }, - ]); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - CrawlCustomSettingsFlyoutLogic.actions.fetchDomainConfigData(); - }); - }); - - describe('hideFlyout', () => { - it('hides the modal', () => { - CrawlCustomSettingsFlyoutLogic.actions.hideFlyout(); - - expect(CrawlCustomSettingsFlyoutLogic.values.isFlyoutVisible).toBe(false); - }); - }); - - describe('onRecieveDomainConfigData', () => { - it('saves the data', () => { - mount({ - domainConfigs: [], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onRecieveDomainConfigData([ - { - name: 'https://www.elastic.co', - }, - ] as DomainConfig[]); - - expect(CrawlCustomSettingsFlyoutLogic.values.domainConfigs).toEqual([ - { - name: 'https://www.elastic.co', - }, - ]); - }); - }); - - describe('onSelectCustomSitemapUrls', () => { - it('saves the urls', () => { - mount({ - customSitemapUrls: [], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectCustomSitemapUrls([ - 'https://www.elastic.co/custom-sitemap1.xml', - 'https://swiftype.com/custom-sitemap2.xml', - ]); - - expect(CrawlCustomSettingsFlyoutLogic.values.customSitemapUrls).toEqual([ - 'https://www.elastic.co/custom-sitemap1.xml', - 'https://swiftype.com/custom-sitemap2.xml', - ]); - }); - }); - - describe('onSelectCustomEntryPointUrls', () => { - it('saves the urls', () => { - mount({ - customEntryPointUrls: [], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectCustomEntryPointUrls([ - 'https://www.elastic.co/custom-entry-point', - 'https://swiftype.com/custom-entry-point', - ]); - - expect(CrawlCustomSettingsFlyoutLogic.values.customEntryPointUrls).toEqual([ - 'https://www.elastic.co/custom-entry-point', - 'https://swiftype.com/custom-entry-point', - ]); - }); - }); - - describe('onSelectDomainUrls', () => { - it('saves the urls', () => { - mount({ - selectedDomainUrls: [], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectDomainUrls([ - 'https://www.elastic.co', - 'https://swiftype.com', - ]); - - expect(CrawlCustomSettingsFlyoutLogic.values.selectedDomainUrls).toEqual([ - 'https://www.elastic.co', - 'https://swiftype.com', - ]); - }); - - it('filters selected sitemap urls by selected domains', () => { - mount({ - selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'], - selectedSitemapUrls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectDomainUrls(['https://swiftype.com']); - - expect(CrawlCustomSettingsFlyoutLogic.values.selectedSitemapUrls).toEqual([ - 'https://swiftype.com/sitemap2.xml', - ]); - }); - - it('filters selected entry point urls by selected domains', () => { - mount({ - selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'], - selectedEntryPointUrls: [ - 'https://www.elastic.co/guide', - 'https://swiftype.com/documentation', - ], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectDomainUrls(['https://swiftype.com']); - - expect(CrawlCustomSettingsFlyoutLogic.values.selectedEntryPointUrls).toEqual([ - 'https://swiftype.com/documentation', - ]); - }); - }); - - describe('onSelectEntryPointUrls', () => { - it('saves the urls', () => { - mount({ - selectedEntryPointUrls: [], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectEntryPointUrls([ - 'https://www.elastic.co/guide', - 'https://swiftype.com/documentation', - ]); - - expect(CrawlCustomSettingsFlyoutLogic.values.selectedEntryPointUrls).toEqual([ - 'https://www.elastic.co/guide', - 'https://swiftype.com/documentation', - ]); - }); - }); - - describe('onSelectMaxCrawlDepth', () => { - it('saves the crawl depth', () => { - mount({ - maxCrawlDepth: 5, - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectMaxCrawlDepth(10); - - expect(CrawlCustomSettingsFlyoutLogic.values.maxCrawlDepth).toEqual(10); - }); - }); - - describe('onSelectSitemapUrls', () => { - it('saves the urls', () => { - mount({ - selectedSitemapUrls: [], - }); - - CrawlCustomSettingsFlyoutLogic.actions.onSelectSitemapUrls([ - 'https://www.elastic.co/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ]); - - expect(CrawlCustomSettingsFlyoutLogic.values.selectedSitemapUrls).toEqual([ - 'https://www.elastic.co/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ]); - }); - }); - - describe('showFlyout', () => { - it('shows the modal and resets the form', () => { - mount({ - customEntryPointUrls: [ - 'https://www.elastic.co/custom-entry-point', - 'https://swiftype.com/custom-entry-point', - ], - customSitemapUrls: [ - 'https://www.elastic.co/custom-sitemap1.xml', - 'https://swiftype.com/custom-sitemap2.xml', - ], - includeSitemapsInRobotsTxt: false, - isDataLoading: false, - isFlyoutVisible: false, - selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'], - selectedEntryPointUrls: [ - 'https://www.elastic.co/guide', - 'https://swiftype.com/documentation', - ], - selectedSitemapUrls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ], - }); - - CrawlCustomSettingsFlyoutLogic.actions.showFlyout(); - - expect(CrawlCustomSettingsFlyoutLogic.values).toEqual( - expect.objectContaining({ - customEntryPointUrls: [], - customSitemapUrls: [], - includeSitemapsInRobotsTxt: true, - isDataLoading: true, - isFlyoutVisible: true, - selectedDomainUrls: [], - selectedEntryPointUrls: [], - selectedSitemapUrls: [], - }) - ); - }); - - it('fetches the latest data', () => { - jest.spyOn(CrawlCustomSettingsFlyoutLogic.actions, 'fetchDomainConfigData'); - - CrawlCustomSettingsFlyoutLogic.actions.showFlyout(); - - expect(CrawlCustomSettingsFlyoutLogic.actions.fetchDomainConfigData).toHaveBeenCalled(); - }); - }); - - describe('startCustomCrawl', () => { - it('can start a custom crawl for selected domains', async () => { - mount({ - includeSitemapsInRobotsTxt: true, - maxCrawlDepth: 5, - selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'], - }); - jest.spyOn(CrawlerLogic.actions, 'startCrawl'); - - CrawlCustomSettingsFlyoutLogic.actions.startCustomCrawl(); - await nextTick(); - - expect(CrawlerLogic.actions.startCrawl).toHaveBeenCalledWith({ - domain_allowlist: ['https://www.elastic.co', 'https://swiftype.com'], - max_crawl_depth: 5, - sitemap_discovery_disabled: false, - }); - }); - - it('can start a custom crawl selected domains, sitemaps, and seed urls', async () => { - mount({ - includeSitemapsInRobotsTxt: true, - maxCrawlDepth: 5, - selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'], - selectedEntryPointUrls: [ - 'https://www.elastic.co/guide', - 'https://swiftype.com/documentation', - ], - selectedSitemapUrls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ], - }); - jest.spyOn(CrawlerLogic.actions, 'startCrawl'); - - CrawlCustomSettingsFlyoutLogic.actions.startCustomCrawl(); - await nextTick(); - - expect(CrawlerLogic.actions.startCrawl).toHaveBeenCalledWith({ - domain_allowlist: ['https://www.elastic.co', 'https://swiftype.com'], - max_crawl_depth: 5, - seed_urls: ['https://www.elastic.co/guide', 'https://swiftype.com/documentation'], - sitemap_urls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ], - sitemap_discovery_disabled: false, - }); - }); - }); - - describe('toggleIncludeSitemapsInRobotsTxt', () => { - it('toggles the flag', () => { - mount({ - includeSitemapsInRobotsTxt: false, - }); - - CrawlCustomSettingsFlyoutLogic.actions.toggleIncludeSitemapsInRobotsTxt(); - - expect(CrawlCustomSettingsFlyoutLogic.values.includeSitemapsInRobotsTxt).toEqual(true); - - mount({ - includeSitemapsInRobotsTxt: true, - }); - - CrawlCustomSettingsFlyoutLogic.actions.toggleIncludeSitemapsInRobotsTxt(); - - expect(CrawlCustomSettingsFlyoutLogic.values.includeSitemapsInRobotsTxt).toEqual(false); - }); - }); - - describe('[CrawlerLogic.actionTypes.startCrawl]', () => { - it('enables loading state', () => { - mount({ - isFormSubmitting: false, - }); - - CrawlerLogic.actions.startCrawl(); - - expect(CrawlCustomSettingsFlyoutLogic.values.isFormSubmitting).toBe(true); - }); - }); - - describe('[CrawlerLogic.actionTypes.onStartCrawlRequestComplete]', () => { - it('disables loading state and hides the modal', () => { - mount({ - isFormSubmitting: true, - isFlyoutVisible: true, - }); - - CrawlerLogic.actions.onStartCrawlRequestComplete(); - - expect(CrawlCustomSettingsFlyoutLogic.values.isFormSubmitting).toBe(false); - expect(CrawlCustomSettingsFlyoutLogic.values.isFlyoutVisible).toBe(false); - }); - }); - }); - - describe('selectors', () => { - beforeEach(() => { - mount({ - domainConfigs: [ - { - name: 'https://www.elastic.co', - sitemapUrls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://www.elastic.co/sitemap2.xml', - ], - seedUrls: ['https://www.elastic.co/', 'https://www.elastic.co/guide'], - }, - { - name: 'https://swiftype.com', - sitemapUrls: ['https://swiftype.com/sitemap1.xml', 'https://swiftype.com/sitemap2.xml'], - seedUrls: ['https://swiftype.com/', 'https://swiftype.com/documentation'], - }, - ], - selectedDomainUrls: ['https://swiftype.com'], - }); - }); - - describe('domainUrls', () => { - it('contains all the domain urls from the domain config', () => { - expect(CrawlCustomSettingsFlyoutLogic.values.domainUrls).toEqual([ - 'https://www.elastic.co', - 'https://swiftype.com', - ]); - }); - }); - - describe('domainConfigMap', () => { - it('contains all the domain urls from the domain config', () => { - expect(CrawlCustomSettingsFlyoutLogic.values.domainConfigMap).toEqual({ - 'https://www.elastic.co': { - name: 'https://www.elastic.co', - sitemapUrls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://www.elastic.co/sitemap2.xml', - ], - seedUrls: ['https://www.elastic.co/', 'https://www.elastic.co/guide'], - }, - 'https://swiftype.com': { - name: 'https://swiftype.com', - sitemapUrls: ['https://swiftype.com/sitemap1.xml', 'https://swiftype.com/sitemap2.xml'], - seedUrls: ['https://swiftype.com/', 'https://swiftype.com/documentation'], - }, - }); - }); - }); - - describe('entryPointUrls', () => { - it('contains all the sitemap urls from selected domains', () => { - expect(CrawlCustomSettingsFlyoutLogic.values.entryPointUrls).toEqual([ - 'https://swiftype.com/', - 'https://swiftype.com/documentation', - ]); - }); - }); - - describe('sitemapUrls', () => { - it('contains all the sitemap urls from selected domains', () => { - expect(CrawlCustomSettingsFlyoutLogic.values.sitemapUrls).toEqual([ - 'https://swiftype.com/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ]); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts deleted file mode 100644 index c1d3d4b9e4a68..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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 { Meta } from '../../../../../../../common/types'; -import { flashAPIErrors } from '../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../shared/http'; -import { EngineLogic } from '../../../engine'; -import { CrawlerLogic, CrawlRequestOverrides } from '../../crawler_logic'; -import { DomainConfig, DomainConfigFromServer } from '../../types'; -import { domainConfigServerToClient } from '../../utils'; -import { extractDomainAndEntryPointFromUrl } from '../add_domain/utils'; - -export interface CrawlCustomSettingsFlyoutLogicValues { - customEntryPointUrls: string[]; - customSitemapUrls: string[]; - domainUrls: string[]; - domainConfigs: DomainConfig[]; - domainConfigMap: { - [key: string]: DomainConfig; - }; - entryPointUrls: string[]; - includeSitemapsInRobotsTxt: boolean; - isDataLoading: boolean; - isFormSubmitting: boolean; - isFlyoutVisible: boolean; - maxCrawlDepth: number; - selectedDomainUrls: string[]; - selectedEntryPointUrls: string[]; - selectedSitemapUrls: string[]; - sitemapUrls: string[]; -} - -export interface CrawlCustomSettingsFlyoutLogicActions { - fetchDomainConfigData(): void; - hideFlyout(): void; - onRecieveDomainConfigData(domainConfigs: DomainConfig[]): { domainConfigs: DomainConfig[] }; - onSelectCustomEntryPointUrls(entryPointUrls: string[]): { entryPointUrls: string[] }; - onSelectCustomSitemapUrls(sitemapUrls: string[]): { sitemapUrls: string[] }; - onSelectDomainUrls(domainUrls: string[]): { domainUrls: string[] }; - onSelectEntryPointUrls(entryPointUrls: string[]): { entryPointUrls: string[] }; - onSelectMaxCrawlDepth(maxCrawlDepth: number): { maxCrawlDepth: number }; - onSelectSitemapUrls(sitemapUrls: string[]): { sitemapUrls: string[] }; - showFlyout(): void; - startCustomCrawl(): void; - toggleIncludeSitemapsInRobotsTxt(): void; -} - -const filterSeedUrlsByDomainUrls = (seedUrls: string[], domainUrls: string[]): string[] => { - const domainUrlMap = domainUrls.reduce((acc, domainUrl) => { - acc[domainUrl] = true; - return acc; - }, {} as { [key: string]: boolean }); - - return seedUrls.filter((seedUrl) => { - const { domain } = extractDomainAndEntryPointFromUrl(seedUrl); - return !!domainUrlMap[domain]; - }); -}; - -export const CrawlCustomSettingsFlyoutLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'crawler', 'crawl_custom_settings_flyout'], - actions: () => ({ - fetchDomainConfigData: true, - hideFlyout: true, - onRecieveDomainConfigData: (domainConfigs) => ({ domainConfigs }), - onSelectCustomEntryPointUrls: (entryPointUrls) => ({ entryPointUrls }), - onSelectCustomSitemapUrls: (sitemapUrls) => ({ sitemapUrls }), - onSelectDomainUrls: (domainUrls) => ({ domainUrls }), - onSelectEntryPointUrls: (entryPointUrls) => ({ entryPointUrls }), - onSelectMaxCrawlDepth: (maxCrawlDepth) => ({ maxCrawlDepth }), - onSelectSitemapUrls: (sitemapUrls) => ({ sitemapUrls }), - startCustomCrawl: true, - toggleIncludeSitemapsInRobotsTxt: true, - showFlyout: true, - }), - reducers: () => ({ - customEntryPointUrls: [ - [], - { - showFlyout: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSelectCustomEntryPointUrls: (_, { entryPointUrls }) => entryPointUrls, - }, - ], - customSitemapUrls: [ - [], - { - showFlyout: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSelectCustomSitemapUrls: (_, { sitemapUrls }) => sitemapUrls, - }, - ], - domainConfigs: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onRecieveDomainConfigData: (_, { domainConfigs }) => domainConfigs, - }, - ], - includeSitemapsInRobotsTxt: [ - true, - { - showFlyout: () => true, - // @ts-expect-error upgrade typescript v5.1.6 - toggleIncludeSitemapsInRobotsTxt: (includeSitemapsInRobotsTxt) => - !includeSitemapsInRobotsTxt, - }, - ], - isDataLoading: [ - true, - { - showFlyout: () => true, - onRecieveDomainConfigData: () => false, - }, - ], - isFormSubmitting: [ - false, - { - [CrawlerLogic.actionTypes.startCrawl]: () => true, - [CrawlerLogic.actionTypes.onStartCrawlRequestComplete]: () => false, - }, - ], - isFlyoutVisible: [ - false, - { - showFlyout: () => true, - hideFlyout: () => false, - [CrawlerLogic.actionTypes.onStartCrawlRequestComplete]: () => false, - }, - ], - maxCrawlDepth: [ - 2, - { - showFlyout: () => 2, - // @ts-expect-error upgrade typescript v5.1.6 - onSelectMaxCrawlDepth: (_, { maxCrawlDepth }) => maxCrawlDepth, - }, - ], - selectedDomainUrls: [ - [], - { - showFlyout: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSelectDomainUrls: (_, { domainUrls }) => domainUrls, - }, - ], - selectedEntryPointUrls: [ - [], - { - showFlyout: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSelectEntryPointUrls: (_, { entryPointUrls }) => entryPointUrls, - // @ts-expect-error upgrade typescript v5.1.6 - onSelectDomainUrls: (entryPointUrls, { domainUrls }) => - filterSeedUrlsByDomainUrls(entryPointUrls, domainUrls), - }, - ], - selectedSitemapUrls: [ - [], - { - showFlyout: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSelectSitemapUrls: (_, { sitemapUrls }) => sitemapUrls, - // @ts-expect-error upgrade typescript v5.1.6 - onSelectDomainUrls: (selectedSitemapUrls, { domainUrls }) => - filterSeedUrlsByDomainUrls(selectedSitemapUrls, domainUrls), - }, - ], - }), - selectors: () => ({ - domainUrls: [ - (selectors) => [selectors.domainConfigs], - (domainConfigs: DomainConfig[]) => domainConfigs.map((domainConfig) => domainConfig.name), - ], - domainConfigMap: [ - (selectors) => [selectors.domainConfigs], - (domainConfigs: DomainConfig[]) => - domainConfigs.reduce((acc, domainConfig) => { - acc[domainConfig.name] = domainConfig; - return acc; - }, {} as { [key: string]: DomainConfig }), - ], - entryPointUrls: [ - (selectors) => [selectors.domainConfigMap, selectors.selectedDomainUrls], - (domainConfigMap: { [key: string]: DomainConfig }, selectedDomainUrls: string[]): string[] => - selectedDomainUrls.flatMap( - (selectedDomainUrl) => domainConfigMap[selectedDomainUrl].seedUrls - ), - ], - sitemapUrls: [ - (selectors) => [selectors.domainConfigMap, selectors.selectedDomainUrls], - (domainConfigMap: { [key: string]: DomainConfig }, selectedDomainUrls: string[]): string[] => - selectedDomainUrls.flatMap( - (selectedDomainUrl) => domainConfigMap[selectedDomainUrl].sitemapUrls - ), - ], - }), - listeners: ({ actions, values }) => ({ - fetchDomainConfigData: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - let domainConfigs: DomainConfig[] = []; - let nextPage: number = 1; - let totalPages: number = 1; - let pageSize: number = 100; - try { - while (nextPage <= totalPages) { - const { - results, - meta: { page }, - } = await http.get<{ - meta: Meta; - results: DomainConfigFromServer[]; - }>(`/internal/app_search/engines/${engineName}/crawler/domain_configs`, { - query: { 'page[current]': nextPage, 'page[size]': pageSize }, - }); - - domainConfigs = [...domainConfigs, ...results.map(domainConfigServerToClient)]; - - nextPage = page.current + 1; - totalPages = page.total_pages; - pageSize = page.size; - } - - actions.onRecieveDomainConfigData(domainConfigs); - } catch (e) { - flashAPIErrors(e); - } - }, - showFlyout: () => { - actions.fetchDomainConfigData(); - }, - startCustomCrawl: () => { - const overrides: CrawlRequestOverrides = { - sitemap_discovery_disabled: !values.includeSitemapsInRobotsTxt, - max_crawl_depth: values.maxCrawlDepth, - domain_allowlist: values.selectedDomainUrls, - }; - - const seedUrls = [...values.selectedEntryPointUrls, ...values.customEntryPointUrls]; - if (seedUrls.length > 0) { - overrides.seed_urls = seedUrls; - } - - const sitemapUrls = [...values.selectedSitemapUrls, ...values.customSitemapUrls]; - if (sitemapUrls.length > 0) { - overrides.sitemap_urls = sitemapUrls; - } - - CrawlerLogic.actions.startCrawl(overrides); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_seed_urls_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_seed_urls_panel.test.tsx deleted file mode 100644 index 7faf7696af497..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_seed_urls_panel.test.tsx +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiAccordion, EuiTabbedContent, EuiNotificationBadge, EuiCheckbox } from '@elastic/eui'; - -import { UrlComboBox } from '../../../../../shared/url_combo_box/url_combo_box'; - -import { rerender } from '../../../../../test_helpers'; -import { SimplifiedSelectable } from '../crawl_select_domains_modal/simplified_selectable'; - -import { CrawlCustomSettingsFlyoutSeedUrlsPanel } from './crawl_custom_settings_flyout_seed_urls_panel'; - -const MOCK_VALUES = { - // CrawlCustomSettingsFlyoutLogic - customEntryPointUrls: ['https://www.elastic.co/custom-entry-point'], - customSitemapUrls: [ - 'https://www.elastic.co/custom-sitemap1.xml', - 'https://swiftype.com/custom-sitemap2.xml', - ], - entryPointUrls: ['https://www.elastic.co/guide', 'https://swiftype.com/documentation'], - selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'], - selectedEntryPointUrls: ['https://swiftype.com/documentation'], - selectedSitemapUrls: ['https://www.elastic.co/sitemap1.xml', 'https://swiftype.com/sitemap2.xml'], - sitemapUrls: [ - 'https://www.elastic.co/sitemap1.xml', - 'https://www.elastic.co/sitemap2.xml', - 'https://swiftype.com/sitemap1.xml', - 'https://swiftype.com/sitemap2.xml', - ], - includeSitemapsInRobotsTxt: true, -}; - -const MOCK_ACTIONS = { - // CrawlCustomSettingsFlyoutLogic - onSelectCustomEntryPointUrls: jest.fn(), - onSelectCustomSitemapUrls: jest.fn(), - onSelectEntryPointUrls: jest.fn(), - onSelectSitemapUrls: jest.fn(), - toggleIncludeSitemapsInRobotsTxt: jest.fn(), -}; - -const getAccordionBadge = (wrapper: ShallowWrapper) => { - const accordionWrapper = wrapper.find(EuiAccordion); - const extraActionWrapper = shallow(
{accordionWrapper.prop('extraAction')}
); - return extraActionWrapper.find(EuiNotificationBadge); -}; - -describe('CrawlCustomSettingsFlyoutSeedUrlsPanel', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - - wrapper = shallow(); - }); - - describe('sitemaps tab', () => { - let sitemapTab: ShallowWrapper; - - beforeEach(() => { - const tabs = wrapper.find(EuiTabbedContent).prop('tabs'); - sitemapTab = shallow(
{tabs[0].content}
); - }); - - it('allows the user to select sitemap urls', () => { - expect(sitemapTab.find(SimplifiedSelectable).props()).toEqual({ - options: MOCK_VALUES.sitemapUrls, - selectedOptions: MOCK_VALUES.selectedSitemapUrls, - onChange: MOCK_ACTIONS.onSelectSitemapUrls, - }); - }); - - it('allows the user to toggle whether to include robots.txt sitemaps', () => { - expect(sitemapTab.find(EuiCheckbox).props()).toEqual( - expect.objectContaining({ - onChange: MOCK_ACTIONS.toggleIncludeSitemapsInRobotsTxt, - checked: true, - }) - ); - }); - - it('allows the user to add custom sitemap urls', () => { - expect(sitemapTab.find(UrlComboBox).props()).toEqual( - expect.objectContaining({ - selectedUrls: MOCK_VALUES.customSitemapUrls, - onChange: MOCK_ACTIONS.onSelectCustomSitemapUrls, - }) - ); - }); - }); - - describe('entry points tab', () => { - let entryPointsTab: ShallowWrapper; - - beforeEach(() => { - const tabs = wrapper.find(EuiTabbedContent).prop('tabs'); - entryPointsTab = shallow(
{tabs[1].content}
); - }); - - it('allows the user to select entry point urls', () => { - expect(entryPointsTab.find(SimplifiedSelectable).props()).toEqual({ - options: MOCK_VALUES.entryPointUrls, - selectedOptions: MOCK_VALUES.selectedEntryPointUrls, - onChange: MOCK_ACTIONS.onSelectEntryPointUrls, - }); - }); - - it('allows the user to add custom entry point urls', () => { - expect(entryPointsTab.find(UrlComboBox).props()).toEqual( - expect.objectContaining({ - selectedUrls: MOCK_VALUES.customEntryPointUrls, - onChange: MOCK_ACTIONS.onSelectCustomEntryPointUrls, - }) - ); - }); - }); - - it('indicates how many seed urls are selected', () => { - let badge = getAccordionBadge(wrapper); - - expect(badge.render().text()).toContain('6'); - expect(badge.prop('color')).toEqual('accent'); - - setMockValues({ - ...MOCK_VALUES, - customEntryPointUrls: [], - customSitemapUrls: [], - selectedEntryPointUrls: [], - selectedSitemapUrls: [], - }); - - rerender(wrapper); - badge = getAccordionBadge(wrapper); - - expect(badge.render().text()).toContain('0'); - expect(badge.prop('color')).toEqual('subdued'); - }); - - it('shows empty messages when the user has not selected any domains', () => { - setMockValues({ - ...MOCK_VALUES, - selectedDomainUrls: [], - }); - - rerender(wrapper); - - const tabs = wrapper.find(EuiTabbedContent).prop('tabs'); - const sitemapsTab = shallow(
{tabs[0].content}
); - const entryPointsTab = shallow(
{tabs[1].content}
); - - expect(sitemapsTab.find(SimplifiedSelectable).prop('emptyMessage')).toBeDefined(); - expect(entryPointsTab.find(SimplifiedSelectable).prop('emptyMessage')).toBeDefined(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_seed_urls_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_seed_urls_panel.tsx deleted file mode 100644 index b72332fa4eeca..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_seed_urls_panel.tsx +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiAccordion, - EuiCheckbox, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIcon, - EuiNotificationBadge, - EuiPanel, - EuiSpacer, - EuiTabbedContent, - EuiTitle, - useGeneratedHtmlId, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { UrlComboBox } from '../../../../../shared/url_combo_box/url_combo_box'; - -import { SimplifiedSelectable } from '../crawl_select_domains_modal/simplified_selectable'; - -import { CrawlCustomSettingsFlyoutLogic } from './crawl_custom_settings_flyout_logic'; - -export const CrawlCustomSettingsFlyoutSeedUrlsPanel: React.FC = () => { - const { - customEntryPointUrls, - customSitemapUrls, - entryPointUrls, - includeSitemapsInRobotsTxt, - selectedDomainUrls, - selectedEntryPointUrls, - selectedSitemapUrls, - sitemapUrls, - } = useValues(CrawlCustomSettingsFlyoutLogic); - const { - onSelectCustomEntryPointUrls, - onSelectCustomSitemapUrls, - onSelectEntryPointUrls, - onSelectSitemapUrls, - toggleIncludeSitemapsInRobotsTxt, - } = useActions(CrawlCustomSettingsFlyoutLogic); - - const totalSeedUrls = - customEntryPointUrls.length + - customSitemapUrls.length + - selectedEntryPointUrls.length + - selectedSitemapUrls.length; - - return ( - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.seedUrlsAccordionButtonLabel', - { - defaultMessage: 'Seed URLs', - } - )} -

-
-
- - } - extraAction={ - - 0 ? 'accent' : 'subdued'}> - {totalSeedUrls} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.selectedDescriptor', - { - defaultMessage: 'selected', - } - )} - - - } - > - - - - robots.txt, // this is a technical term and shouldn't be translated - }} - /> - } - checked={includeSitemapsInRobotsTxt} - onChange={toggleIncludeSitemapsInRobotsTxt} - /> - - - - - - ), - }, - { - id: useGeneratedHtmlId({ prefix: 'entryPointsTab' }), - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlCustomSettingsFlyout.entryPointsTabLabel', - { - defaultMessage: 'Entry points', - } - ), - content: ( - <> - - - - - - ), - }, - ]} - autoFocus="selected" - /> -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_flyout.test.tsx deleted file mode 100644 index 9f46d6750590f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_flyout.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/shallow_useeffect.mock'; -import '../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCodeBlock, EuiFlyout, EuiTab, EuiTabs } from '@elastic/eui'; - -import { Loading } from '../../../../../shared/loading'; - -import { CrawlRequestWithDetailsFromServer } from '../../types'; - -import { CrawlDetailsPreview } from './crawl_details_preview'; - -import { CrawlDetailsFlyout } from '.'; - -const MOCK_VALUES = { - dataLoading: false, - flyoutClosed: false, - crawlRequestFromServer: {} as CrawlRequestWithDetailsFromServer, - logRetention: { - crawler: { - enabled: true, - }, - }, -}; - -const MOCK_ACTIONS = { - setSelectedTab: jest.fn(), - fetchLogRetention: jest.fn(), -}; - -describe('CrawlDetailsFlyout', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders a flyout ', () => { - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - - const wrapper = shallow(); - - expect(wrapper.is(EuiFlyout)).toBe(true); - }); - - it('contains a tab group to control displayed content inside the flyout', () => { - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - - const wrapper = shallow(); - const tabs = wrapper.find(EuiTabs).find(EuiTab); - - expect(tabs).toHaveLength(2); - - tabs.at(0).simulate('click'); - - expect(MOCK_ACTIONS.setSelectedTab).toHaveBeenCalledWith('preview'); - - tabs.at(1).simulate('click'); - - expect(MOCK_ACTIONS.setSelectedTab).toHaveBeenCalledWith('json'); - }); - - describe('when the preview tab is selected', () => { - beforeEach(() => { - setMockValues({ - ...MOCK_VALUES, - selectedTab: 'preview', - }); - }); - - it('shows the correct tab is selected in the UX', () => { - const wrapper = shallow(); - const tabs = wrapper.find(EuiTabs).find(EuiTab); - - expect(tabs.at(0).prop('isSelected')).toBe(true); - expect(tabs.at(1).prop('isSelected')).toBe(false); - }); - - it('shows the human readable version of the crawl details', () => { - const wrapper = shallow(); - - const crawlDetailsPreview = wrapper.find(CrawlDetailsPreview); - expect(crawlDetailsPreview).toHaveLength(1); - expect(crawlDetailsPreview.prop('crawlerLogsEnabled')).toEqual(true); - }); - - it('shows the preview differently if the crawler logs are disabled', () => { - setMockValues({ - ...MOCK_VALUES, - selectedTab: 'preview', - logRetention: null, - }); - const wrapper = shallow(); - - const crawlDetailsPreview = wrapper.find(CrawlDetailsPreview); - expect(crawlDetailsPreview).toHaveLength(1); - expect(crawlDetailsPreview.prop('crawlerLogsEnabled')).toEqual(false); - }); - }); - - describe('when the json tab is selected', () => { - beforeEach(() => { - setMockValues({ - ...MOCK_VALUES, - selectedTab: 'json', - }); - }); - - it('shows the correct tab is selected in the UX', () => { - const wrapper = shallow(); - const tabs = wrapper.find(EuiTabs).find(EuiTab); - - expect(tabs.at(0).prop('isSelected')).toBe(false); - expect(tabs.at(1).prop('isSelected')).toBe(true); - }); - - it('shows the raw json of the crawl details', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCodeBlock)).toHaveLength(1); - }); - }); - - it('renders a loading screen when loading', () => { - setMockValues({ ...MOCK_VALUES, dataLoading: true }); - - const wrapper = shallow(); - - expect(wrapper.is(EuiFlyout)).toBe(true); - expect(wrapper.find(Loading)).toHaveLength(1); - }); - - it('is empty when the flyout is hidden', () => { - setMockValues({ - flyoutClosed: true, - }); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_flyout.tsx deleted file mode 100644 index f1bfd3e5e5e9b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_flyout.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiFlyout, - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiCodeBlock, - EuiTab, - EuiTabs, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { Loading } from '../../../../../shared/loading'; -import { LogRetentionLogic } from '../../../log_retention'; -import { CrawlDetailLogic } from '../../crawl_detail_logic'; - -import { CrawlDetailsPreview } from './crawl_details_preview'; - -export const CrawlDetailsFlyout: React.FC = () => { - const { closeFlyout, setSelectedTab } = useActions(CrawlDetailLogic); - const { crawlRequestFromServer, dataLoading, flyoutClosed, selectedTab } = - useValues(CrawlDetailLogic); - const { fetchLogRetention } = useActions(LogRetentionLogic); - const { logRetention } = useValues(LogRetentionLogic); - - useEffect(() => { - fetchLogRetention(); - }, []); - - if (flyoutClosed) { - return null; - } - - return ( - - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.title', { - defaultMessage: 'Crawl request details', - })} -

-
- - setSelectedTab('preview')}> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.previewTabLabel', - { - defaultMessage: 'Preview', - } - )} - - setSelectedTab('json')}> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsFlyout.rawJSONTabLabel', - { - defaultMessage: 'Raw JSON', - } - )} - - -
- - {dataLoading ? ( - - ) : ( - <> - {selectedTab === 'preview' && ( - - )} - {selectedTab === 'json' && ( - - {JSON.stringify(crawlRequestFromServer, null, 2)} - - )} - - )} - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_preview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_preview.test.tsx deleted file mode 100644 index 07871fe7dd7ec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_preview.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { set } from '@kbn/safer-lodash-set/fp'; - -import { AccordionList } from '../../../../../shared/accordion_list/accordion_list'; - -import { CrawlDetailValues } from '../../crawl_detail_logic'; -import { CrawlerStatus, CrawlType } from '../../types'; - -import { CrawlDetailsPreview } from './crawl_details_preview'; -import { CrawlDetailsSummary } from './crawl_details_summary'; - -const MOCK_VALUES: Partial = { - crawlRequest: { - id: '507f1f77bcf86cd799439011', - status: CrawlerStatus.Pending, - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: ['https://www.elastic.co', 'https://www.swiftype.com'], - seedUrls: ['https://www.elastic.co/docs', 'https://www.swiftype.com/documentation'], - sitemapUrls: ['https://www.elastic.co/sitemap.xml', 'https://www.swiftype.com/sitemap.xml'], - maxCrawlDepth: 10, - }, - stats: { - status: { - urlsAllowed: 10, - pagesVisited: 10, - crawlDurationMSec: 36000, - avgResponseTimeMSec: 100, - }, - }, - }, -}; - -describe('CrawlDetailsPreview', () => { - it('is empty when a crawl request has not been loaded', () => { - setMockValues({ - crawlRequest: null, - }); - - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(true); - }); - - describe('when a crawl request has been loaded', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - setMockValues(MOCK_VALUES); - wrapper = shallow(); - }); - - it('contains a summary', () => { - const summary = wrapper.find(CrawlDetailsSummary); - expect(summary.props()).toEqual({ - crawlDepth: 10, - crawlType: 'full', - crawlerLogsEnabled: true, - domainCount: 2, - stats: { - status: { - avgResponseTimeMSec: 100, - crawlDurationMSec: 36000, - pagesVisited: 10, - urlsAllowed: 10, - }, - }, - }); - }); - - it('will default values on summary if missing', () => { - const values = set('crawlRequest.stats', undefined, MOCK_VALUES); - setMockValues(values); - wrapper = shallow(); - - const summary = wrapper.find(CrawlDetailsSummary); - expect(summary.prop('crawlerLogsEnabled')).toEqual(false); - expect(summary.prop('stats')).toEqual(null); - }); - - it('contains a list of domains', () => { - const domainList = wrapper.find(AccordionList).at(0); - - expect(domainList.prop('items')).toEqual([ - 'https://www.elastic.co', - 'https://www.swiftype.com', - ]); - }); - - it('contains a list of seed urls', () => { - const seedUrlList = wrapper.find(AccordionList).at(1); - - expect(seedUrlList.prop('items')).toEqual([ - 'https://www.elastic.co/docs', - 'https://www.swiftype.com/documentation', - ]); - }); - - it('contains a list of sitemap urls', () => { - const sitemapUrlList = wrapper.find(AccordionList).at(2); - - expect(sitemapUrlList.prop('items')).toEqual([ - 'https://www.elastic.co/sitemap.xml', - 'https://www.swiftype.com/sitemap.xml', - ]); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_preview.tsx deleted file mode 100644 index 6afe9badf1fd1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_preview.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AccordionList } from '../../../../../shared/accordion_list/accordion_list'; -import { CrawlDetailLogic } from '../../crawl_detail_logic'; - -import { CrawlDetailsSummary } from './crawl_details_summary'; - -interface CrawlDetailsPreviewProps { - crawlerLogsEnabled?: boolean; -} - -export const CrawlDetailsPreview: React.FC = ({ - crawlerLogsEnabled = false, -}) => { - const { crawlRequest } = useValues(CrawlDetailLogic); - - if (crawlRequest === null) { - return null; - } - - return ( - <> - - - 0} - title={i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.domainsTitle', - { - defaultMessage: 'Domains', - } - )} - iconType="globe" - items={crawlRequest.crawlConfig.domainAllowlist} - /> - - 0} - title={i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.seedUrlsTitle', - { - defaultMessage: 'Seed URLs', - } - )} - iconType="crosshairs" - items={crawlRequest.crawlConfig.seedUrls} - /> - - 0} - title={i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsPreview.sitemapUrlsTitle', - { - defaultMessage: 'Sitemap URLs', - } - )} - iconType="visMapRegion" - items={crawlRequest.crawlConfig.sitemapUrls} - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_summary.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_summary.test.tsx deleted file mode 100644 index f37060a9cef42..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_summary.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 '../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiPanel } from '@elastic/eui'; - -import { CrawlDetailsSummary } from './crawl_details_summary'; - -const MOCK_PROPS = { - crawlDepth: 8, - crawlerLogsEnabled: true, - crawlType: 'full', - domainCount: 15, - stats: { - status: { - urlsAllowed: 108, - crawlDurationMSec: 748382, - pagesVisited: 108, - avgResponseTimeMSec: 42, - statusCodes: { - 401: 4, - 404: 8, - 500: 0, - 503: 3, - }, - }, - }, -}; - -describe('CrawlDetailsSummary', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - wrapper = shallow(); - }); - - it('renders as a panel with all fields', () => { - expect(wrapper.is(EuiPanel)).toBe(true); - }); - - it('renders the proper count for errors', () => { - const serverErrors = wrapper.find({ 'data-test-subj': 'serverErrors' }); - const clientErrors = wrapper.find({ 'data-test-subj': 'clientErrors' }); - - expect(serverErrors.prop('title')).toEqual(3); - expect(clientErrors.prop('title')).toEqual(12); - }); - - it('handles missing stats gracefully', () => { - wrapper.setProps({ stats: {} }); - expect(wrapper.find({ 'data-test-subj': 'crawlDuration' }).prop('title')).toEqual('--'); - expect(wrapper.find({ 'data-test-subj': 'pagesVisited' }).prop('title')).toEqual('--'); - expect(wrapper.find({ 'data-test-subj': 'avgResponseTime' }).prop('title')).toEqual('--'); - }); - - it('renders the stat object when logs are disabled but stats are not null', () => { - wrapper.setProps({ crawlerLogsEnabled: false }); - expect(wrapper.find({ 'data-test-subj': 'crawlDuration' })).toHaveLength(1); - expect(wrapper.find({ 'data-test-subj': 'pagesVisited' })).toHaveLength(1); - expect(wrapper.find({ 'data-test-subj': 'avgResponseTime' })).toHaveLength(1); - expect(wrapper.find({ 'data-test-subj': 'urlsAllowed' })).toHaveLength(1); - expect(wrapper.find({ 'data-test-subj': 'logsDisabledMessage' })).toHaveLength(0); - }); - - it('renders a message to enable logs when crawler logs are disabled and stats are null', () => { - wrapper.setProps({ crawlerLogsEnabled: false, stats: null }); - expect(wrapper.find({ 'data-test-subj': 'crawlDuration' })).toHaveLength(0); - expect(wrapper.find({ 'data-test-subj': 'pagesVisited' })).toHaveLength(0); - expect(wrapper.find({ 'data-test-subj': 'avgResponseTime' })).toHaveLength(0); - expect(wrapper.find({ 'data-test-subj': 'urlsAllowed' })).toHaveLength(0); - expect(wrapper.find({ 'data-test-subj': 'logsDisabledMessage' })).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_summary.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_summary.tsx deleted file mode 100644 index 0c5217806534d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/crawl_details_summary.tsx +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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 moment from 'moment'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiIconTip, - EuiPanel, - EuiSpacer, - EuiStat, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CrawlRequestStats } from '../../types'; - -interface ICrawlerSummaryProps { - crawlDepth: number; - crawlType: string; - crawlerLogsEnabled: boolean; - domainCount: number; - stats: CrawlRequestStats | null; -} - -export const CrawlDetailsSummary: React.FC = ({ - crawlDepth, - crawlType, - crawlerLogsEnabled, - domainCount, - stats, -}) => { - const duration = () => { - if (stats && stats.status && stats.status.crawlDurationMSec) { - const milliseconds = moment.duration(stats.status.crawlDurationMSec, 'milliseconds'); - const days = milliseconds.days(); - const hours = milliseconds.hours(); - const minutes = milliseconds.minutes(); - const seconds = milliseconds.seconds(); - return `${days ? days + 'd ' : ''}${hours}h ${minutes}m ${seconds}s`; - } else { - return '--'; - } - }; - - const getStatusCount = (code: string, codes: { [code: string]: number }) => { - return Object.entries(codes).reduce((count, [k, v]) => { - if (k[0] !== code) return count; - return v + count; - }, 0); - }; - - const statusCounts = { - clientErrorCount: - stats && stats.status && stats.status.statusCodes - ? getStatusCount('4', stats.status.statusCodes) - : 0, - serverErrorCount: - stats && stats.status && stats.status.statusCodes - ? getStatusCount('5', stats.status.statusCodes) - : 0, - }; - - const shouldHideStats = !crawlerLogsEnabled && !stats; - - return ( - - - - - - - - - {!shouldHideStats && ( - - - - )} - - - {!shouldHideStats ? ( - - - - URLs{' '} - - - } - /> - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.pagesVisitedTooltipTitle', - { - defaultMessage: 'Pages', - } - )}{' '} - - - } - /> - - - - - - - - - - - - ) : ( - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlDetailsSummary.logsDisabledMessage', - { - defaultMessage: - 'Enable Web Crawler logs in settings for more detailed crawl statistics.', - } - )} -

-
- )} -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/index.ts deleted file mode 100644 index 2a3327679b914..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_details_flyout/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { CrawlDetailsFlyout } from './crawl_details_flyout'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_event_type_badge.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_event_type_badge.test.tsx deleted file mode 100644 index bc5efc7714495..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_event_type_badge.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 '../../../__mocks__/engine_logic.mock'; - -import { shallow } from 'enzyme'; - -import { EuiBadge } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { CrawlEvent, CrawlerStatus, CrawlType } from '../types'; - -import { CrawlEventTypeBadge } from './crawl_event_type_badge'; - -const MOCK_EVENT: CrawlEvent = { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - stage: 'crawl', - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: ['https://www.elastic.co'], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, -}; - -describe('CrawlEventTypeBadge', () => { - it('renders a badge for process crawls', () => { - const wrapper = mountWithIntl( - - ); - - const badge = wrapper.find(EuiBadge); - expect(badge.prop('color')).toEqual('hollow'); - expect(badge.text()).toEqual('Re-applied crawl rules'); - }); - - it('renders a badge for partial crawls', () => { - const wrapper = mountWithIntl( - - ); - - const badge = wrapper.find(EuiBadge); - expect(badge.prop('color')).toEqual('hollow'); - expect(badge.text()).toEqual('Partial'); - }); - - it('renders a badge for full crawls', () => { - const wrapper = mountWithIntl( - - ); - - const badge = wrapper.find(EuiBadge); - expect(badge.prop('color')).toBeUndefined(); - expect(badge.text()).toEqual('Full'); - }); - - it('is empty by default', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_event_type_badge.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_event_type_badge.tsx deleted file mode 100644 index 6195b5616a30b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_event_type_badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { EuiBadge } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CrawlEvent, CrawlType, readableCrawlTypes } from '../types'; - -export const CrawlEventTypeBadge: React.FC<{ event: CrawlEvent }> = ({ event }) => { - if (event.stage === 'process') { - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.reAppliedCrawlRules', - { - defaultMessage: 'Re-applied crawl rules', - } - )} - - ); - } - if (event.type === CrawlType.Full) { - return {readableCrawlTypes[CrawlType.Full]}; - } - if (event.type === CrawlType.Partial) { - return {readableCrawlTypes[CrawlType.Partial]}; - } - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_requests_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_requests_table.test.tsx deleted file mode 100644 index dfab4d1086a2f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_requests_table.test.tsx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiBasicTable } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { CrawlEvent, CrawlerStatus, CrawlType } from '../types'; - -import { CrawlRequestsTable } from './crawl_requests_table'; - -const values: { events: CrawlEvent[] } = { - // CrawlerLogic - events: [ - { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - stage: 'crawl', - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: ['https://www.elastic.co'], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, - }, - { - id: '54325423aef7890543', - status: CrawlerStatus.Success, - stage: 'process', - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: ['https://www.elastic.co'], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, - }, - ], -}; - -const actions = { - fetchCrawlRequest: jest.fn(), - openFlyout: jest.fn(), -}; - -describe('CrawlRequestsTable', () => { - let wrapper: ShallowWrapper; - let tableContent: string; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('columns', () => { - beforeAll(() => { - setMockActions(actions); - setMockValues(values); - wrapper = shallow(); - tableContent = mountWithIntl() - .find(EuiBasicTable) - .text(); - }); - - it('renders a id column ', () => { - expect(tableContent).toContain('Request ID'); - - const table = wrapper.find(EuiBasicTable); - const columns = table.prop('columns'); - - const crawlID = shallow(columns[0].render('618d0e66abe97bc688328900', { stage: 'crawl' })); - expect(crawlID.text()).toContain('618d0e66abe97bc688328900'); - - crawlID.simulate('click'); - expect(actions.fetchCrawlRequest).toHaveBeenCalledWith('618d0e66abe97bc688328900'); - expect(actions.openFlyout).toHaveBeenCalled(); - - const processCrawlID = shallow(columns[0].render('54325423aef7890543', { stage: 'process' })); - expect(processCrawlID.text()).toContain('54325423aef7890543'); - }); - - it('renders a created at column', () => { - expect(tableContent).toContain('Created'); - expect(tableContent).toContain('Aug 31, 2020'); - }); - - it('renders a type column', () => { - expect(tableContent).toContain('Crawl type'); - expect(tableContent).toContain('Full'); - }); - - it('renders a domains column', () => { - expect(tableContent).toContain('Domains'); - // TODO How to test for the contents of this badge? - }); - - it('renders a status column', () => { - expect(tableContent).toContain('Status'); - expect(tableContent).toContain('Pending'); - }); - }); - - describe('no items message', () => { - it('displays an empty prompt when there are no crawl requests', () => { - setMockValues({ - ...values, - events: [], - }); - - wrapper = shallow(); - - expect(wrapper.render().find('.euiEmptyPrompt')).toHaveLength(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_requests_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_requests_table.tsx deleted file mode 100644 index f57f8ba4942f0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_requests_table.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 { - EuiBadge, - EuiBasicTable, - EuiTableFieldDataColumnType, - EuiTableComputedColumnType, - EuiEmptyPrompt, - EuiLink, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CrawlDetailLogic } from '../crawl_detail_logic'; -import { CrawlerLogic } from '../crawler_logic'; -import { CrawlEvent, readableCrawlerStatuses } from '../types'; - -import { CrawlEventTypeBadge } from './crawl_event_type_badge'; -import { CustomFormattedTimestamp } from './custom_formatted_timestamp'; - -export const CrawlRequestsTable: React.FC = () => { - const { events } = useValues(CrawlerLogic); - const { fetchCrawlRequest, openFlyout } = useActions(CrawlDetailLogic); - - const columns: Array< - EuiTableFieldDataColumnType | EuiTableComputedColumnType - > = [ - { - field: 'id', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domainURL', - { - defaultMessage: 'Request ID', - } - ), - render: (id: string, event: CrawlEvent) => { - if (event.stage === 'crawl') { - return ( - { - fetchCrawlRequest(id); - openFlyout(); - }} - > - {id} - - ); - } - return {id}; - }, - }, - { - field: 'createdAt', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.created', - { - defaultMessage: 'Created', - } - ), - render: (createdAt: CrawlEvent['createdAt']) => ( - - ), - }, - { - field: 'type', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.crawlType', - { - defaultMessage: 'Crawl type', - } - ), - render: (_, event: CrawlEvent) => , - }, - { - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.domains', - { - defaultMessage: 'Domains', - } - ), - render: (event: CrawlEvent) => ( - {event.crawlConfig.domainAllowlist.length} - ), - }, - { - field: 'status', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.column.status', - { - defaultMessage: 'Status', - } - ), - render: (status: CrawlEvent['status']) => readableCrawlerStatuses[status], - }, - ]; - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.title', - { - defaultMessage: 'No recent crawl requests', - } - )} - - } - body={ -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.body', - { - defaultMessage: "You haven't started any crawls yet.", - } - )} -

- } - /> - } - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.test.tsx deleted file mode 100644 index 27624a088ddd7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.test.tsx +++ /dev/null @@ -1,341 +0,0 @@ -/* - * 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 { mockFlashMessageHelpers, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiFieldText, EuiSelect } from '@elastic/eui'; - -import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; - -import { CrawlerPolicies, CrawlerRules } from '../types'; - -import { CrawlRulesTable } from './crawl_rules_table'; - -describe('CrawlRulesTable', () => { - const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; - const engineName = 'my-engine'; - const crawlRules = [ - { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, - { id: '2', pattern: '*', policy: CrawlerPolicies.deny, rule: CrawlerRules.endsWith }, - ]; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(GenericEndpointInlineEditableTable).exists()).toBe(true); - }); - - describe('columns', () => { - const crawlRule = { - id: '1', - pattern: '*', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.beginsWith, - }; - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - const renderColumn = (index: number) => { - const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); - return shallow(
{columns[index].render(crawlRule)}
); - }; - - const onChange = jest.fn(); - const renderColumnInEditingMode = (index: number) => { - const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); - return shallow( -
- {columns[index].editingRender(crawlRule, onChange, { - isInvalid: false, - isLoading: false, - })} -
- ); - }; - - describe('policy column', () => { - it('shows the policy of a crawl rule', () => { - expect(renderColumn(0).html()).toContain('Allow'); - }); - - it('can show the policy of a crawl rule as editable', () => { - const column = renderColumnInEditingMode(0); - - const selectField = column.find(EuiSelect); - expect(selectField.props()).toEqual( - expect.objectContaining({ - value: 'allow', - disabled: false, - isInvalid: false, - options: [ - { text: 'Allow', value: 'allow' }, - { text: 'Disallow', value: 'deny' }, - ], - }) - ); - - selectField.simulate('change', { target: { value: 'deny' } }); - expect(onChange).toHaveBeenCalledWith('deny'); - }); - }); - - describe('rule column', () => { - it('shows the rule of a crawl rule', () => { - expect(renderColumn(1).html()).toContain('Begins with'); - }); - - it('can show the rule of a crawl rule as editable', () => { - const column = renderColumnInEditingMode(1); - - const selectField = column.find(EuiSelect); - expect(selectField.props()).toEqual( - expect.objectContaining({ - value: 'begins', - disabled: false, - isInvalid: false, - options: [ - { text: 'Begins with', value: 'begins' }, - { text: 'Ends with', value: 'ends' }, - { text: 'Contains', value: 'contains' }, - { text: 'Regex', value: 'regex' }, - ], - }) - ); - - selectField.simulate('change', { target: { value: 'ends' } }); - expect(onChange).toHaveBeenCalledWith('ends'); - }); - }); - - describe('pattern column', () => { - it('shows the pattern of a crawl rule', () => { - expect(renderColumn(2).html()).toContain('*'); - }); - - it('can show the pattern of a crawl rule as editable', () => { - const column = renderColumnInEditingMode(2); - - const field = column.find(EuiFieldText); - expect(field.props()).toEqual( - expect.objectContaining({ - value: '*', - disabled: false, - isInvalid: false, - }) - ); - - field.simulate('change', { target: { value: 'foo' } }); - expect(onChange).toHaveBeenCalledWith('foo'); - }); - }); - }); - - describe('routes', () => { - it('can calculate an update and delete route correctly', () => { - const wrapper = shallow( - - ); - - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const crawlRule = { - id: '1', - pattern: '*', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.beginsWith, - }; - expect(table.prop('deleteRoute')(crawlRule)).toEqual( - '/internal/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/crawl_rules/1' - ); - expect(table.prop('updateRoute')(crawlRule)).toEqual( - '/internal/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/crawl_rules/1' - ); - }); - }); - - it('shows a custom description if one is provided', () => { - const wrapper = shallow( - - ); - - const table = wrapper.find(GenericEndpointInlineEditableTable); - expect(table.prop('description')).toEqual('I am a description'); - }); - - it('shows a default crawl rule as uneditable if one is provided', () => { - const wrapper = shallow( - - ); - - const table = wrapper.find(GenericEndpointInlineEditableTable); - expect(table.prop('uneditableItems')).toEqual([crawlRules[0]]); - }); - - describe('when a crawl rule is added', () => { - it('should update the crawl rules for the current domain, and clear flash messages', () => { - const updateCrawlRules = jest.fn(); - setMockActions({ - updateCrawlRules, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const crawlRulesThatWasAdded = { - id: '2', - pattern: '*', - policy: CrawlerPolicies.deny, - rule: CrawlerRules.endsWith, - }; - const updatedCrawlRules = [ - { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, - { id: '2', pattern: '*', policy: CrawlerPolicies.deny, rule: CrawlerRules.endsWith }, - ]; - table.prop('onAdd')(crawlRulesThatWasAdded, updatedCrawlRules); - expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - - describe('when a crawl rule is updated', () => { - it('should update the crawl rules for the current domain, and clear flash messages', () => { - const updateCrawlRules = jest.fn(); - setMockActions({ - updateCrawlRules, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const crawlRulesThatWasUpdated = { - id: '2', - pattern: '*', - policy: CrawlerPolicies.deny, - rule: CrawlerRules.endsWith, - }; - const updatedCrawlRules = [ - { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, - { - id: '2', - pattern: 'newPattern', - policy: CrawlerPolicies.deny, - rule: CrawlerRules.endsWith, - }, - ]; - table.prop('onUpdate')(crawlRulesThatWasUpdated, updatedCrawlRules); - expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - - describe('when a crawl rule is deleted', () => { - it('should update the crawl rules for the current domain, clear flash messages, and show a success', () => { - const updateCrawlRules = jest.fn(); - setMockActions({ - updateCrawlRules, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const crawlRulesThatWasDeleted = { - id: '2', - pattern: '*', - policy: CrawlerPolicies.deny, - rule: CrawlerRules.endsWith, - }; - const updatedCrawlRules = [ - { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, - ]; - table.prop('onDelete')(crawlRulesThatWasDeleted, updatedCrawlRules); - expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); - expect(clearFlashMessages).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - }); - - describe('when a crawl rule is reordered', () => { - it('should update the crawl rules for the current domain and clear flash messages', () => { - const updateCrawlRules = jest.fn(); - setMockActions({ - updateCrawlRules, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const updatedCrawlRules = [ - { id: '2', pattern: '*', policy: CrawlerPolicies.deny, rule: CrawlerRules.endsWith }, - { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, - ]; - table.prop('onReorder')!(updatedCrawlRules); - expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx deleted file mode 100644 index 89cf2248201ce..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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 } from 'kea'; - -import { - EuiCode, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiIconTip, - EuiLink, - EuiSelect, - EuiText, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../../shared/doc_links'; -import { clearFlashMessages, flashSuccessToast } from '../../../../shared/flash_messages'; -import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; -import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; -import { ItemWithAnID } from '../../../../shared/tables/types'; -import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; -import { - CrawlerPolicies, - CrawlerRules, - CrawlRule, - getReadableCrawlerPolicy, - getReadableCrawlerRule, -} from '../types'; -import { getCrawlRulePathPatternTooltip } from '../utils'; - -interface CrawlRulesTableProps { - description?: React.ReactNode; - domainId: string; - engineName: string; - crawlRules: CrawlRule[]; - defaultCrawlRule?: CrawlRule; -} - -const DEFAULT_DESCRIPTION = ( -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.descriptionLinkText', - { defaultMessage: 'Learn more about crawl rules' } - )} - - ), - }} - /> -

-); - -export const CrawlRulesTable: React.FC = ({ - description = DEFAULT_DESCRIPTION, - domainId, - engineName, - crawlRules, - defaultCrawlRule, -}) => { - const { updateCrawlRules } = useActions(CrawlerSingleDomainLogic); - - const columns: Array> = [ - { - editingRender: (crawlRule, onChange, { isInvalid, isLoading }) => ( - onChange(e.target.value)} - disabled={isLoading} - isInvalid={isInvalid} - options={[CrawlerPolicies.allow, CrawlerPolicies.deny].map( - (policyOption: CrawlerPolicies) => ({ - text: getReadableCrawlerPolicy(policyOption), - value: policyOption, - }) - )} - /> - ), - render: (crawlRule) => ( - {getReadableCrawlerPolicy((crawlRule as CrawlRule).policy)} - ), - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.policyTableHead', - { - defaultMessage: 'Policy', - } - ), - field: 'policy', - }, - { - editingRender: (crawlRule, onChange, { isInvalid, isLoading }) => ( - onChange(e.target.value)} - disabled={isLoading} - isInvalid={isInvalid} - options={[ - CrawlerRules.beginsWith, - CrawlerRules.endsWith, - CrawlerRules.contains, - CrawlerRules.regex, - ].map((ruleOption: CrawlerRules) => ({ - text: getReadableCrawlerRule(ruleOption), - value: ruleOption, - }))} - /> - ), - render: (crawlRule) => ( - {getReadableCrawlerRule((crawlRule as CrawlRule).rule)} - ), - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.ruleTableHead', - { - defaultMessage: 'Rule', - } - ), - field: 'rule', - }, - { - editingRender: (crawlRule, onChange, { isInvalid, isLoading }) => ( - - - onChange(e.target.value)} - disabled={isLoading} - isInvalid={isInvalid} - /> - - - - - - ), - render: (crawlRule) => {(crawlRule as CrawlRule).pattern}, - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTableHead', - { - defaultMessage: 'Path pattern', - } - ), - field: 'pattern', - }, - ]; - - const crawlRulesRoute = `/internal/app_search/engines/${engineName}/crawler/domains/${domainId}/crawl_rules`; - const domainRoute = `/internal/app_search/engines/${engineName}/crawler/domains/${domainId}`; - const getCrawlRuleRoute = (crawlRule: CrawlRule) => - `/internal/app_search/engines/${engineName}/crawler/domains/${domainId}/crawl_rules/${crawlRule.id}`; - - return ( - { - updateCrawlRules(newCrawlRules as CrawlRule[]); - clearFlashMessages(); - }} - onDelete={(_, newCrawlRules) => { - updateCrawlRules(newCrawlRules as CrawlRule[]); - clearFlashMessages(); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.deleteSuccessToastMessage', - { - defaultMessage: 'The crawl rule has been deleted.', - } - ) - ); - }} - onUpdate={(_, newCrawlRules) => { - updateCrawlRules(newCrawlRules as CrawlRule[]); - clearFlashMessages(); - }} - onReorder={(newCrawlRules) => { - updateCrawlRules(newCrawlRules as CrawlRule[]); - clearFlashMessages(); - }} - title={i18n.translate('xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.title', { - defaultMessage: 'Crawl rules', - })} - uneditableItems={defaultCrawlRule ? [defaultCrawlRule] : undefined} - canRemoveLastItem - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.scss deleted file mode 100644 index 09abf97829be4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.scss +++ /dev/null @@ -1,4 +0,0 @@ -.crawlSelectDomainsModal { - width: 50rem; - max-width: 90%; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.test.tsx deleted file mode 100644 index 79898d9f15e9d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiModal, EuiModalFooter, EuiButton, EuiButtonEmpty } from '@elastic/eui'; - -import { rerender } from '../../../../../test_helpers'; - -import { CrawlSelectDomainsModal } from './crawl_select_domains_modal'; -import { SimplifiedSelectable } from './simplified_selectable'; - -const MOCK_VALUES = { - // CrawlerLogic - domains: [{ url: 'https://www.elastic.co' }, { url: 'https://www.swiftype.com' }], - // CrawlSelectDomainsModalLogic - selectedDomainUrls: ['https://www.elastic.co'], - isModalVisible: true, -}; - -const MOCK_ACTIONS = { - // CrawlSelectDomainsModalLogic - hideModal: jest.fn(), - onSelectDomainUrls: jest.fn(), - // CrawlerLogic - startCrawl: jest.fn(), -}; - -describe('CrawlSelectDomainsModal', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - - wrapper = shallow(); - }); - - it('is empty when the modal is hidden', () => { - setMockValues({ - ...MOCK_VALUES, - isModalVisible: false, - }); - - rerender(wrapper); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('renders as a modal when visible', () => { - expect(wrapper.is(EuiModal)).toBe(true); - }); - - it('can be closed', () => { - expect(wrapper.prop('onClose')).toEqual(MOCK_ACTIONS.hideModal); - expect(wrapper.find(EuiModalFooter).find(EuiButtonEmpty).prop('onClick')).toEqual( - MOCK_ACTIONS.hideModal - ); - }); - - it('allows the user to select domains', () => { - expect(wrapper.find(SimplifiedSelectable).props()).toEqual({ - options: ['https://www.elastic.co', 'https://www.swiftype.com'], - selectedOptions: ['https://www.elastic.co'], - onChange: MOCK_ACTIONS.onSelectDomainUrls, - }); - }); - - describe('submit button', () => { - it('is disabled when no domains are selected', () => { - setMockValues({ - ...MOCK_VALUES, - selectedDomainUrls: [], - }); - - rerender(wrapper); - - expect(wrapper.find(EuiModalFooter).find(EuiButton).prop('disabled')).toEqual(true); - }); - - it('starts a crawl and hides the modal', () => { - wrapper.find(EuiModalFooter).find(EuiButton).simulate('click'); - - expect(MOCK_ACTIONS.startCrawl).toHaveBeenCalledWith({ - domain_allowlist: MOCK_VALUES.selectedDomainUrls, - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.tsx deleted file mode 100644 index 211266a779df9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiNotificationBadge, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants'; - -import { CrawlerLogic } from '../../crawler_logic'; - -import { CrawlSelectDomainsModalLogic } from './crawl_select_domains_modal_logic'; -import { SimplifiedSelectable } from './simplified_selectable'; - -import './crawl_select_domains_modal.scss'; - -export const CrawlSelectDomainsModal: React.FC = () => { - const { domains } = useValues(CrawlerLogic); - const domainUrls = domains.map((domain) => domain.url); - - const crawlSelectDomainsModalLogic = CrawlSelectDomainsModalLogic({ domains }); - const { isDataLoading, isModalVisible, selectedDomainUrls } = useValues( - crawlSelectDomainsModalLogic - ); - const { hideModal, onSelectDomainUrls } = useActions(crawlSelectDomainsModalLogic); - - const { startCrawl } = useActions(CrawlerLogic); - - if (!isModalVisible) { - return null; - } - - return ( - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.modalHeaderTitle', - { - defaultMessage: 'Crawl select domains', - } - )} - - - 0 ? 'accent' : 'subdued'} - > - {selectedDomainUrls.length} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.selectedDescriptor', - { - defaultMessage: 'selected', - } - )} - - - - - - - - {CANCEL_BUTTON_LABEL} - { - startCrawl({ domain_allowlist: selectedDomainUrls }); - }} - disabled={selectedDomainUrls.length === 0} - isLoading={isDataLoading} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.startCrawlButtonLabel', - { - defaultMessage: 'Apply and crawl now', - } - )} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal_logic.test.ts deleted file mode 100644 index ef6ef4d09fadb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal_logic.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 { CrawlerLogic } from '../../crawler_logic'; - -import { CrawlSelectDomainsModalLogic } from './crawl_select_domains_modal_logic'; - -describe('CrawlSelectDomainsModalLogic', () => { - const { mount } = new LogicMounter(CrawlSelectDomainsModalLogic); - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(CrawlSelectDomainsModalLogic.values).toEqual({ - isDataLoading: false, - isModalVisible: false, - selectedDomainUrls: [], - }); - }); - - describe('actions', () => { - describe('hideModal', () => { - it('hides the modal', () => { - CrawlSelectDomainsModalLogic.actions.hideModal(); - - expect(CrawlSelectDomainsModalLogic.values.isModalVisible).toBe(false); - }); - }); - - describe('showModal', () => { - it('shows the modal', () => { - CrawlSelectDomainsModalLogic.actions.showModal(); - - expect(CrawlSelectDomainsModalLogic.values.isModalVisible).toBe(true); - }); - - it('resets the selected options', () => { - mount({ - selectedDomainUrls: ['https://www.elastic.co', 'https://www.swiftype.com'], - }); - - CrawlSelectDomainsModalLogic.actions.showModal(); - - expect(CrawlSelectDomainsModalLogic.values.selectedDomainUrls).toEqual([]); - }); - }); - - describe('onSelectDomainUrls', () => { - it('saves the urls', () => { - mount({ - selectedDomainUrls: [], - }); - - CrawlSelectDomainsModalLogic.actions.onSelectDomainUrls([ - 'https://www.elastic.co', - 'https://www.swiftype.com', - ]); - - expect(CrawlSelectDomainsModalLogic.values.selectedDomainUrls).toEqual([ - 'https://www.elastic.co', - 'https://www.swiftype.com', - ]); - }); - }); - - describe('[CrawlerLogic.actionTypes.startCrawl]', () => { - it('enables loading state', () => { - mount({ - isDataLoading: false, - }); - - CrawlerLogic.actions.startCrawl(); - - expect(CrawlSelectDomainsModalLogic.values.isDataLoading).toBe(true); - }); - }); - - describe('[CrawlerLogic.actionTypes.onStartCrawlRequestComplete]', () => { - it('disables loading state and hides the modal', () => { - mount({ - isDataLoading: true, - isModalVisible: true, - }); - - CrawlerLogic.actions.onStartCrawlRequestComplete(); - - expect(CrawlSelectDomainsModalLogic.values.isDataLoading).toBe(false); - expect(CrawlSelectDomainsModalLogic.values.isModalVisible).toBe(false); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal_logic.ts deleted file mode 100644 index 8bfd0874ae8cb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/crawl_select_domains_modal_logic.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 { CrawlerLogic } from '../../crawler_logic'; - -import { CrawlerDomain } from '../../types'; - -export interface CrawlSelectDomainsLogicProps { - domains: CrawlerDomain[]; -} - -export interface CrawlSelectDomainsLogicValues { - isDataLoading: boolean; - isModalVisible: boolean; - selectedDomainUrls: string[]; -} - -export interface CrawlSelectDomainsModalLogicActions { - hideModal(): void; - onSelectDomainUrls(domainUrls: string[]): { domainUrls: string[] }; - showModal(): void; -} - -export const CrawlSelectDomainsModalLogic = kea< - MakeLogicType< - CrawlSelectDomainsLogicValues, - CrawlSelectDomainsModalLogicActions, - CrawlSelectDomainsLogicProps - > ->({ - path: ['enterprise_search', 'app_search', 'crawler', 'crawl_select_domains_modal'], - actions: () => ({ - hideModal: true, - onSelectDomainUrls: (domainUrls) => ({ domainUrls }), - showModal: true, - }), - reducers: () => ({ - isDataLoading: [ - false, - { - [CrawlerLogic.actionTypes.startCrawl]: () => true, - [CrawlerLogic.actionTypes.onStartCrawlRequestComplete]: () => false, - }, - ], - isModalVisible: [ - false, - { - showModal: () => true, - hideModal: () => false, - [CrawlerLogic.actionTypes.onStartCrawlRequestComplete]: () => false, - }, - ], - selectedDomainUrls: [ - [], - { - showModal: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onSelectDomainUrls: (_, { domainUrls }) => domainUrls, - }, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/simplified_selectable.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/simplified_selectable.tsx deleted file mode 100644 index 5f57477b7ae5c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_select_domains_modal/simplified_selectable.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SimplifiedSelectable } from '../../../../../shared/simplified_selectable/simplified_selectable'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_banner.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_banner.test.tsx deleted file mode 100644 index 1be4c27037d1f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_banner.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCallOut } from '@elastic/eui'; - -import { CrawlerStatus } from '../types'; - -import { CrawlerStatusBanner } from './crawler_status_banner'; - -describe('CrawlerStatusBanner', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - [(CrawlerStatus.Starting, CrawlerStatus.Running, CrawlerStatus.Canceling)].forEach((status) => { - describe(`when the status is ${status}`, () => { - it('renders a callout', () => { - setMockValues({ - mostRecentCrawlRequestStatus: status, - }); - - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - }); - }); - }); - - [ - CrawlerStatus.Success, - CrawlerStatus.Failed, - CrawlerStatus.Canceled, - CrawlerStatus.Pending, - CrawlerStatus.Suspended, - CrawlerStatus.Suspending, - ].forEach((status) => { - describe(`when the status is ${status}`, () => { - it('does not render a banner/callout', () => { - setMockValues({ - mostRecentCrawlRequestStatus: status, - }); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_banner.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_banner.tsx deleted file mode 100644 index 527eff6ab25a5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_banner.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiCallOut } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CrawlerLogic } from '../crawler_logic'; -import { CrawlerStatus } from '../types'; - -export const CrawlerStatusBanner: React.FC = () => { - const { mostRecentCrawlRequestStatus } = useValues(CrawlerLogic); - if ( - mostRecentCrawlRequestStatus === CrawlerStatus.Running || - mostRecentCrawlRequestStatus === CrawlerStatus.Starting || - mostRecentCrawlRequestStatus === CrawlerStatus.Canceling - ) { - return ( - - ); - } - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx deleted file mode 100644 index cc8b1891838b3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { CrawlerDomain, CrawlerStatus } from '../../types'; - -import { CrawlerStatusIndicator } from './crawler_status_indicator'; -import { StartCrawlContextMenu } from './start_crawl_context_menu'; -import { StopCrawlPopoverContextMenu } from './stop_crawl_popover_context_menu'; - -const MOCK_VALUES = { - domains: [{}, {}] as CrawlerDomain[], - mostRecentCrawlRequestStatus: CrawlerStatus.Success, -}; - -const MOCK_ACTIONS = { - startCrawl: jest.fn(), - stopCrawl: jest.fn(), -}; - -describe('CrawlerStatusIndicator', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - }); - - describe('when status is not a valid status', () => { - it('is disabled', () => { - // this tests a codepath that should be impossible to reach, status should always be a CrawlerStatus - // but we use a switch statement and need to test the default case for this to receive 100% coverage - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: null, - }); - - const wrapper = shallow(); - expect(wrapper.is(EuiButton)).toEqual(true); - expect(wrapper.render().text()).toContain('Start a crawl'); - expect(wrapper.prop('disabled')).toEqual(true); - }); - }); - - describe('when there are no domains', () => { - it('is disabled', () => { - setMockValues({ - ...MOCK_VALUES, - domains: [], - }); - - const wrapper = shallow(); - expect(wrapper.is(EuiButton)).toEqual(true); - expect(wrapper.render().text()).toContain('Start a crawl'); - expect(wrapper.prop('disabled')).toEqual(true); - }); - }); - - describe('when the status is success', () => { - it('renders an CrawlerStatusIndicator with a start crawl button', () => { - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: CrawlerStatus.Success, - }); - - const wrapper = shallow(); - expect(wrapper.is(StartCrawlContextMenu)).toEqual(true); - }); - }); - - [CrawlerStatus.Failed, CrawlerStatus.Canceled].forEach((status) => { - describe(`when the status is ready for retry: ${status}`, () => { - it('renders an CrawlerStatusIndicator with a retry crawl button', () => { - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: status, - }); - - const wrapper = shallow(); - expect(wrapper.is(StartCrawlContextMenu)).toEqual(true); - }); - }); - }); - - [CrawlerStatus.Pending, CrawlerStatus.Suspended].forEach((status) => { - describe(`when the status is ${status}`, () => { - it('renders an CrawlerStatusIndicator with a pending indicator', () => { - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: status, - }); - - const wrapper = shallow(); - expect(wrapper.is(EuiButton)).toEqual(true); - expect(wrapper.render().text()).toContain('Pending...'); - expect(wrapper.prop('disabled')).toEqual(true); - expect(wrapper.prop('isLoading')).toEqual(true); - }); - }); - }); - - describe('when the status is Starting', () => { - it('renders an appropriate CrawlerStatusIndicator', () => { - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: CrawlerStatus.Starting, - }); - - const wrapper = shallow(); - expect(wrapper.is(EuiButton)).toEqual(true); - expect(wrapper.render().text()).toContain('Starting...'); - expect(wrapper.prop('isLoading')).toEqual(true); - }); - }); - - describe('when the status is Running', () => { - it('renders a stop crawl popover menu', () => { - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: CrawlerStatus.Running, - }); - - const wrapper = shallow(); - expect(wrapper.is(StopCrawlPopoverContextMenu)).toEqual(true); - expect(wrapper.prop('stopCrawl')).toEqual(MOCK_ACTIONS.stopCrawl); - }); - }); - - [CrawlerStatus.Canceling, CrawlerStatus.Suspending].forEach((status) => { - describe(`when the status is ${status}`, () => { - it('renders an CrawlerStatusIndicator with a stopping indicator', () => { - setMockValues({ - ...MOCK_VALUES, - mostRecentCrawlRequestStatus: status, - }); - - const wrapper = shallow(); - expect(wrapper.is(EuiButton)).toEqual(true); - expect(wrapper.render().text()).toContain('Stopping...'); - expect(wrapper.prop('isLoading')).toEqual(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.tsx deleted file mode 100644 index d750cf100202f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/crawler_status_indicator.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CrawlerLogic } from '../../crawler_logic'; -import { CrawlerStatus } from '../../types'; - -import { StartCrawlContextMenu } from './start_crawl_context_menu'; -import { StopCrawlPopoverContextMenu } from './stop_crawl_popover_context_menu'; - -export const CrawlerStatusIndicator: React.FC = () => { - const { domains, mostRecentCrawlRequestStatus } = useValues(CrawlerLogic); - const { stopCrawl } = useActions(CrawlerLogic); - - const disabledButton = ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startACrawlButtonLabel', - { - defaultMessage: 'Start a crawl', - } - )} - - ); - - if (domains.length === 0) { - return disabledButton; - } - - switch (mostRecentCrawlRequestStatus) { - case CrawlerStatus.Success: - return ( - - ); - case CrawlerStatus.Failed: - case CrawlerStatus.Canceled: - return ( - - ); - case CrawlerStatus.Pending: - case CrawlerStatus.Suspended: - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.pendingButtonLabel', - { - defaultMessage: 'Pending...', - } - )} - - ); - case CrawlerStatus.Starting: - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.startingButtonLabel', - { - defaultMessage: 'Starting...', - } - )} - - ); - case CrawlerStatus.Running: - return ; - case CrawlerStatus.Canceling: - case CrawlerStatus.Suspending: - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.stoppingButtonLabel', - { - defaultMessage: 'Stopping...', - } - )} - - ); - default: - // We should never get here, you would have to pass a CrawlerStatus option not covered - // in the switch cases above - return disabledButton; - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/start_crawl_context_menu.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/start_crawl_context_menu.test.tsx deleted file mode 100644 index df68bc8aa6c96..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/start_crawl_context_menu.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { ReactWrapper, shallow } from 'enzyme'; - -import { - EuiContextMenuItem, - EuiContextMenuPanel, - EuiPopover, - EuiResizeObserver, -} from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { StartCrawlContextMenu } from './start_crawl_context_menu'; - -const MOCK_ACTIONS = { - // CrawlerLogic - startCrawl: jest.fn(), - // CrawlCustomSettingsFlyoutLogic - showFlyout: jest.fn(), - // CrawlSelectDomainsModalLogic - showModal: jest.fn(), -}; - -describe('StartCrawlContextMenu', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - }); - - it('is initially closed', () => { - const wrapper = shallow(); - - expect(wrapper.is(EuiPopover)).toBe(true); - expect(wrapper.prop('isOpen')).toEqual(false); - }); - - describe('user actions', () => { - let wrapper: ReactWrapper; - let menuItems: ReactWrapper; - - beforeEach(() => { - wrapper = mountWithIntl(); - - wrapper.find('button').simulate('click'); - - menuItems = wrapper - .find(EuiContextMenuPanel) - .find(EuiResizeObserver) - .find(EuiContextMenuItem); - }); - - it('can be opened', () => { - expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); - expect(menuItems.length).toEqual(3); - }); - - it('can start crawls', () => { - menuItems.at(0).simulate('click'); - - expect(MOCK_ACTIONS.startCrawl).toHaveBeenCalled(); - }); - - it('can open a modal to start a crawl with selected domains', () => { - menuItems.at(1).simulate('click'); - - expect(MOCK_ACTIONS.showModal).toHaveBeenCalled(); - }); - - it('can open a modal to start a crawl with custom settings', () => { - menuItems.at(2).simulate('click'); - - expect(MOCK_ACTIONS.showFlyout).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/start_crawl_context_menu.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/start_crawl_context_menu.tsx deleted file mode 100644 index 57a91a11bb6b3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/start_crawl_context_menu.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useActions } from 'kea'; - -import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CrawlerLogic } from '../../crawler_logic'; - -import { CrawlCustomSettingsFlyoutLogic } from '../crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic'; -import { CrawlSelectDomainsModalLogic } from '../crawl_select_domains_modal/crawl_select_domains_modal_logic'; - -interface Props { - menuButtonLabel?: string; - fill?: boolean; -} - -export const StartCrawlContextMenu: React.FC = ({ menuButtonLabel, fill }) => { - const { startCrawl } = useActions(CrawlerLogic); - const { showModal: showCrawlSelectDomainsModal } = useActions(CrawlSelectDomainsModalLogic); - const { showFlyout: showCrawlCustomSettingsFlyout } = useActions(CrawlCustomSettingsFlyoutLogic); - const [isPopoverOpen, setPopover] = useState(false); - - const togglePopover = () => setPopover(!isPopoverOpen); - - const closePopover = () => setPopover(false); - - return ( - - {menuButtonLabel} - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - { - closePopover(); - startCrawl(); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlAllDomainsMenuLabel', - { - defaultMessage: 'Crawl all domains on this engine', - } - )} - , - { - closePopover(); - showCrawlSelectDomainsModal(); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlSelectDomainsMenuLabel', - { - defaultMessage: 'Crawl select domains', - } - )} - , - { - closePopover(); - showCrawlCustomSettingsFlyout(); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.startCrawlContextMenu.crawlCustomSettingsMenuLabel', - { - defaultMessage: 'Crawl with custom settings', - } - )} - , - ]} - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/stop_crawl_popover_context_menu.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/stop_crawl_popover_context_menu.test.tsx deleted file mode 100644 index 807587ae30058..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/stop_crawl_popover_context_menu.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { - EuiContextMenuItem, - EuiContextMenuPanel, - EuiPopover, - EuiResizeObserver, -} from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { StopCrawlPopoverContextMenu } from './stop_crawl_popover_context_menu'; - -const stopCrawl = jest.fn(); - -describe('StopCrawlsPopoverContextMenu', () => { - it('is initially closed', () => { - const wrapper = shallow(); - - expect(wrapper.is(EuiPopover)).toBe(true); - expect(wrapper.prop('isOpen')).toEqual(false); - }); - - it('can be opened to stop crawls', () => { - const wrapper = mountWithIntl(); - - wrapper.find('button').simulate('click'); - - expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true); - - const menuItem = wrapper - .find(EuiContextMenuPanel) - .find(EuiResizeObserver) - .find(EuiContextMenuItem); - - expect(menuItem).toHaveLength(1); - - menuItem.simulate('click'); - - expect(stopCrawl).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/stop_crawl_popover_context_menu.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/stop_crawl_popover_context_menu.tsx deleted file mode 100644 index 6c0e91995e281..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawler_status_indicator/stop_crawl_popover_context_menu.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { - EuiButton, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPopover, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -interface StopCrawlPopoverContextMenuProps { - stopCrawl(): void; -} - -export const StopCrawlPopoverContextMenu: React.FC = ({ - stopCrawl, - ...rest -}) => { - const [isPopoverOpen, setPopover] = useState(false); - - const togglePopover = () => setPopover(!isPopoverOpen); - - const closePopover = () => setPopover(false); - - return ( - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.crawlingButtonLabel', - { - defaultMessage: 'Crawling...', - } - )} - - - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - { - closePopover(); - stopCrawl(); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.cancelCrawlMenuItemLabel', - { - defaultMessage: 'Cancel Crawl', - } - )} - , - ]} - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx deleted file mode 100644 index ec011e06e0c25..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/custom_formatted_timestamp.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { CustomFormattedTimestamp } from '../../../../shared/custom_formatted_timestamp/custom_formatted_timestamp'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.scss deleted file mode 100644 index 68b0d3e819aab..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.scss +++ /dev/null @@ -1,13 +0,0 @@ -.deduplicationPanel { - .selectableWrapper { - padding: $euiSize; - border-radius: $euiSize *.675; - border: $euiBorderThin solid $euiColorLightestShade; - } - - .showAllFieldsPopoverToggle { - .euiButtonEmpty { - padding-inline: $euiSizeM; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.test.tsx deleted file mode 100644 index 9c076c5550a34..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.test.tsx +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { act } from 'react-dom/test-utils'; - -import { - EuiButton, - EuiButtonEmpty, - EuiContextMenuItem, - EuiPopover, - EuiSelectable, - EuiSelectableList, - EuiSelectableSearch, - EuiSwitch, -} from '@elastic/eui'; - -import { mountWithIntl, rerender } from '../../../../../test_helpers'; - -import { DataPanel } from '../../../data_panel'; - -import { DeduplicationPanel } from './deduplication_panel'; - -const MOCK_ACTIONS = { - submitDeduplicationUpdate: jest.fn(), -}; - -const MOCK_VALUES = { - domain: { - deduplicationEnabled: true, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }, -}; - -describe('DeduplicationPanel', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - }); - - it('renders an empty component if no domain', () => { - setMockValues({ - ...MOCK_VALUES, - domain: null, - }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('contains a button to reset to defaults', () => { - const wrapper = shallow(); - - wrapper.find(DataPanel).dive().find(EuiButton).simulate('click'); - - expect(MOCK_ACTIONS.submitDeduplicationUpdate).toHaveBeenCalledWith(MOCK_VALUES.domain, { - fields: [], - }); - }); - - it('contains a switch to enable and disable deduplication', () => { - setMockValues({ - ...MOCK_VALUES, - domain: { - ...MOCK_VALUES.domain, - deduplicationEnabled: false, - }, - }); - const wrapper = shallow(); - - wrapper.find(EuiSwitch).simulate('change'); - - expect(MOCK_ACTIONS.submitDeduplicationUpdate).toHaveBeenNthCalledWith( - 1, - { - ...MOCK_VALUES.domain, - deduplicationEnabled: false, - }, - { - enabled: true, - } - ); - - setMockValues({ - ...MOCK_VALUES, - domain: { - ...MOCK_VALUES.domain, - deduplicationEnabled: true, - }, - }); - rerender(wrapper); - - wrapper.find(EuiSwitch).simulate('change'); - - expect(MOCK_ACTIONS.submitDeduplicationUpdate).toHaveBeenNthCalledWith( - 2, - { - ...MOCK_VALUES.domain, - deduplicationEnabled: true, - }, - { - enabled: false, - fields: [], - } - ); - }); - - it('contains a popover to switch between displaying all fields or only selected ones', () => { - const fullRender = mountWithIntl(); - - expect(fullRender.find(EuiButtonEmpty).text()).toEqual('All fields'); - expect(fullRender.find(EuiPopover).prop('isOpen')).toEqual(false); - - // Open the popover - fullRender.find(EuiButtonEmpty).simulate('click'); - rerender(fullRender); - - expect(fullRender.find(EuiPopover).prop('isOpen')).toEqual(true); - - // Click "Show selected fields" - fullRender.find(EuiContextMenuItem).at(1).simulate('click'); - rerender(fullRender); - - expect(fullRender.find(EuiButtonEmpty).text()).toEqual('Selected fields'); - expect(fullRender.find(EuiPopover).prop('isOpen')).toEqual(false); - - // Open the popover and click "show all fields" - fullRender.find(EuiButtonEmpty).simulate('click'); - fullRender.find(EuiContextMenuItem).at(0).simulate('click'); - rerender(fullRender); - - expect(fullRender.find(EuiButtonEmpty).text()).toEqual('All fields'); - expect(fullRender.find(EuiPopover).prop('isOpen')).toEqual(false); - - // Open the popover then simulate closing the popover - fullRender.find(EuiButtonEmpty).simulate('click'); - act(() => { - fullRender.find(EuiPopover).prop('closePopover')(); - }); - rerender(fullRender); - - expect(fullRender.find(EuiPopover).prop('isOpen')).toEqual(false); - }); - - it('contains a selectable to toggle fields for deduplication', () => { - const wrapper = shallow(); - - wrapper - .find(EuiSelectable) - .simulate('change', [{ label: 'title' }, { label: 'description', checked: 'on' }]); - - expect(MOCK_ACTIONS.submitDeduplicationUpdate).toHaveBeenCalledWith(MOCK_VALUES.domain, { - fields: ['description'], - }); - - const fullRender = mountWithIntl(); - - expect(fullRender.find(EuiSelectableSearch)).toHaveLength(1); - expect(fullRender.find(EuiSelectableList)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx deleted file mode 100644 index ef4d7448a5785..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiPopover, - EuiSelectable, - EuiSpacer, - EuiSwitch, -} from '@elastic/eui'; - -import { EuiSelectableLIOption } from '@elastic/eui/src/components/selectable/selectable_option'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../../../shared/doc_links'; -import { DataPanel } from '../../../data_panel'; -import { CrawlerSingleDomainLogic } from '../../crawler_single_domain_logic'; - -import { getCheckedOptionLabels, getSelectableOptions } from './utils'; - -import './deduplication_panel.scss'; - -export const DeduplicationPanel: React.FC = () => { - const { domain } = useValues(CrawlerSingleDomainLogic); - const { submitDeduplicationUpdate } = useActions(CrawlerSingleDomainLogic); - - const [showAllFields, setShowAllFields] = useState(true); - const [showAllFieldsPopover, setShowAllFieldsPopover] = useState(false); - - if (!domain) { - return null; - } - - const { deduplicationEnabled, deduplicationFields } = domain; - - const selectableOptions = getSelectableOptions(domain, showAllFields); - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.title', { - defaultMessage: 'Duplicate document handling', - })} - - } - action={ - submitDeduplicationUpdate(domain, { fields: [] })} - disabled={deduplicationFields.length === 0} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.resetToDefaultsButtonLabel', - { - defaultMessage: 'Reset to defaults', - } - )} - - } - subtitle={ - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.learnMoreMessage', - { - defaultMessage: 'Learn more about content hashing', - } - )} - - ), - }} - /> - } - > - - deduplicationEnabled - ? submitDeduplicationUpdate(domain, { enabled: false, fields: [] }) - : submitDeduplicationUpdate(domain, { enabled: true }) - } - /> - - - -
- - submitDeduplicationUpdate(domain, { - fields: getCheckedOptionLabels(options as Array>), - }) - } - searchable - searchProps={{ - disabled: !deduplicationEnabled, - append: ( - setShowAllFieldsPopover(!showAllFieldsPopover)} - className="showAllFieldsPopoverToggle" - disabled={!deduplicationEnabled} - > - {showAllFields - ? i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.allFieldsLabel', - { - defaultMessage: 'All fields', - } - ) - : i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.selectedFieldsLabel', - { - defaultMessage: 'Selected fields', - } - )} - - } - isOpen={showAllFieldsPopover} - closePopover={() => setShowAllFieldsPopover(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - { - setShowAllFields(true); - setShowAllFieldsPopover(false); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.showAllFieldsButtonLabel', - { - defaultMessage: 'Show all fields', - } - )} - , - { - setShowAllFields(false); - setShowAllFieldsPopover(false); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusIndicator.showSelectedFieldsButtonLabel', - { - defaultMessage: 'Show only selected fields', - } - )} - , - ]} - /> - - ), - }} - > - {(list, search) => ( - <> - {search} - {list} - - )} - -
-
-
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/index.ts deleted file mode 100644 index 23545e91a7a69..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { DeduplicationPanel } from './deduplication_panel'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/utils.test.ts deleted file mode 100644 index 58d8e1effa159..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/utils.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { EuiSelectableLIOption } from '@elastic/eui/src/components/selectable/selectable_option'; - -import { CrawlerDomain } from '../../types'; - -import { getCheckedOptionLabels, getSelectableOptions } from './utils'; - -describe('getCheckedOptionLabels', () => { - it('returns the labels of selected options', () => { - const options = [{ label: 'title' }, { label: 'description', checked: 'on' }] as Array< - EuiSelectableLIOption - >; - - expect(getCheckedOptionLabels(options)).toEqual(['description']); - }); -}); - -describe('getSelectableOptions', () => { - it('returns all available fields when we want all fields', () => { - expect( - getSelectableOptions( - { - availableDeduplicationFields: ['title', 'description'], - deduplicationFields: ['title'], - deduplicationEnabled: true, - } as CrawlerDomain, - true - ) - ).toEqual([ - { label: 'title', checked: 'on' }, - { label: 'description', checked: undefined }, - ]); - }); - - it('can returns only selected fields', () => { - expect( - getSelectableOptions( - { - availableDeduplicationFields: ['title', 'description'], - deduplicationFields: ['title'], - deduplicationEnabled: true, - } as CrawlerDomain, - false - ) - ).toEqual([{ label: 'title', checked: 'on' }]); - }); - - it('disables all options when deduplication is disabled', () => { - expect( - getSelectableOptions( - { - availableDeduplicationFields: ['title', 'description'], - deduplicationFields: ['title'], - deduplicationEnabled: false, - } as CrawlerDomain, - true - ) - ).toEqual([ - { label: 'title', checked: 'on', disabled: true }, - { label: 'description', checked: undefined, disabled: true }, - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/utils.ts deleted file mode 100644 index f0ef7ece0c6a6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { EuiSelectableLIOption } from '@elastic/eui/src/components/selectable/selectable_option'; - -import { CrawlerDomain } from '../../types'; - -export const getSelectableOptions = ( - domain: CrawlerDomain, - showAllFields: boolean -): Array> => { - const { availableDeduplicationFields, deduplicationFields, deduplicationEnabled } = domain; - - let selectableOptions: Array>; - - if (showAllFields) { - selectableOptions = availableDeduplicationFields.map((field) => ({ - label: field, - checked: deduplicationFields.includes(field) ? 'on' : undefined, - })); - } else { - selectableOptions = availableDeduplicationFields - .filter((field) => deduplicationFields.includes(field)) - .map((field) => ({ label: field, checked: 'on' })); - } - - if (!deduplicationEnabled) { - selectableOptions = selectableOptions.map((option) => ({ ...option, disabled: true })); - } - - return selectableOptions; -}; - -export const getCheckedOptionLabels = (options: Array>): string[] => { - return options.filter((option) => option.checked).map((option) => option.label); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/delete_domain_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/delete_domain_panel.test.tsx deleted file mode 100644 index 46c2c2c12b83a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/delete_domain_panel.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { DeleteDomainPanel } from './delete_domain_panel'; - -const MOCK_VALUES = { - domain: { id: '9876' }, -}; - -const MOCK_ACTIONS = { - deleteDomain: jest.fn(), -}; - -describe('DeleteDomainPanel', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('contains a button to delete the domain', () => { - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - - const confirmSpy = jest.spyOn(window, 'confirm'); - confirmSpy.mockImplementation(jest.fn(() => true)); - - const wrapper = shallow(); - wrapper.find(EuiButton).simulate('click'); - - expect(MOCK_ACTIONS.deleteDomain).toHaveBeenCalledWith(MOCK_VALUES.domain); - }); - - it("doesn't throw if the users chooses not to confirm", () => { - setMockValues(MOCK_VALUES); - - const confirmSpy = jest.spyOn(window, 'confirm'); - confirmSpy.mockImplementation(jest.fn(() => false)); - - const wrapper = shallow(); - wrapper.find(EuiButton).simulate('click'); - }); - - // The user should never encounter this state, the containing AppSearchTemplate should be loading until - // the relevant domain has been loaded. However we must account for the possibility in this component. - it('is empty if domain has not yet been set', () => { - setMockValues({ - ...MOCK_VALUES, - domain: null, - }); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/delete_domain_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/delete_domain_panel.tsx deleted file mode 100644 index af17e5485d3f7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/delete_domain_panel.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; -import { getDeleteDomainConfirmationMessage } from '../utils'; - -export const DeleteDomainPanel: React.FC = ({}) => { - const { domain } = useValues(CrawlerSingleDomainLogic); - const { deleteDomain } = useActions(CrawlerSingleDomainLogic); - - if (!domain) { - return null; - } - - return ( - <> - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.title', { - defaultMessage: 'Delete domain', - })} -

-
- - -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.cannotUndoMessage', - { - defaultMessage: 'This cannot be undone', - } - )} - - ), - }} - /> -

-
- - { - if (confirm(getDeleteDomainConfirmationMessage(domain.url))) { - deleteDomain(domain); - } - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.deleteDomainButtonLabel', - { - defaultMessage: 'Delete domain', - } - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx deleted file mode 100644 index 143c965a495c7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.test.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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 { mockKibanaValues, setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; - -import { DEFAULT_META } from '../../../../shared/constants'; -import { mountWithIntl } from '../../../../test_helpers'; - -import { CrawlerDomain } from '../types'; - -import { DomainsTable } from './domains_table'; - -const domains: CrawlerDomain[] = [ - { - id: '1234', - documentCount: 9999, - url: 'elastic.co', - crawlRules: [], - entryPoints: [], - sitemaps: [], - lastCrawl: '2020-01-01T00:00:00-12:00', - createdOn: '2020-01-01T00:00:00-12:00', - deduplicationEnabled: false, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }, - { - id: '4567', - documentCount: 0, - url: 'empty.site', - crawlRules: [], - entryPoints: [], - sitemaps: [], - createdOn: '1970-01-01T00:00:00-12:00', - deduplicationEnabled: false, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }, -]; - -const values = { - // EngineLogic - engineName: 'some-engine', - // CrawlerDomainsLogic - domains, - meta: DEFAULT_META, - dataLoading: false, - // AppLogic - myRole: { canManageEngineCrawler: false }, -}; - -const actions = { - // CrawlerDomainsLogic - deleteDomain: jest.fn(), - fetchCrawlerDomainsData: jest.fn(), - onPaginate: jest.fn(), -}; - -describe('DomainsTable', () => { - let wrapper: ShallowWrapper; - let tableContent: string; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - beforeAll(() => { - setMockValues(values); - setMockActions(actions); - wrapper = shallow(); - tableContent = mountWithIntl() - .find(EuiBasicTable) - .text(); - }); - - const getTableBody = () => - // @ts-expect-error upgrade typescript v5.1.6 - wrapper.find(EuiBasicTable).dive().find('RenderWithEuiTheme').renderProp('children')(); - - it('renders', () => { - expect(wrapper.find(EuiBasicTable)).toHaveLength(1); - - expect(wrapper.find(EuiBasicTable).prop('pagination')).toEqual({ - showPerPageOptions: false, - pageIndex: 0, - pageSize: 10, - totalItemCount: 0, - }); - - wrapper.find(EuiBasicTable).simulate('change', { page: { index: 2 } }); - expect(actions.onPaginate).toHaveBeenCalledWith(3); - }); - - describe('columns', () => { - it('renders a url column', () => { - expect(tableContent).toContain('elastic.co'); - }); - - it('renders a clickable domain url', () => { - const link = getTableBody().find('[data-test-subj="CrawlerDomainURL"]').at(0); - - expect(link.dive().text()).toContain('elastic.co'); - expect(link.props()).toEqual( - expect.objectContaining({ - to: '/engines/some-engine/crawler/domains/1234', - }) - ); - }); - - it('renders a last crawled column', () => { - expect(tableContent).toContain('Last activity'); - expect(tableContent).toContain('Jan 1, 2020'); - }); - - it('renders a document count column', () => { - expect(tableContent).toContain('Documents'); - expect(tableContent).toContain('9,999'); - }); - - describe('actions column', () => { - const getActions = () => getTableBody().find('ExpandedItemActions'); - const getActionItems = () => getActions().first().dive().find('DefaultItemAction'); - - it('will hide the action buttons if the user cannot manage/delete engines', () => { - setMockValues({ - ...values, - // AppLogic - myRole: { canManageEngineCrawler: false }, - }); - wrapper = shallow(); - - expect(getActions()).toHaveLength(0); - }); - - describe('when the user can manage/delete engines', () => { - const simulatedClickEvent = { persist: () => {} }; // Required for EUI action clicks. Can be removed if switching away from Enzyme to RTL - - const getManageAction = () => getActionItems().at(0).dive().find(EuiButtonIcon); - const getDeleteAction = () => getActionItems().at(1).dive().find(EuiButtonIcon); - - beforeEach(() => { - setMockValues({ - ...values, - // AppLogic - myRole: { canManageEngineCrawler: true }, - }); - wrapper = shallow(); - }); - - describe('manage action', () => { - it('sends the user to the engine overview on click', () => { - const { navigateToUrl } = mockKibanaValues; - - getManageAction().simulate('click', simulatedClickEvent); - - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/crawler/domains/1234'); - }); - }); - - describe('delete action', () => { - it('clicking the action and confirming deletes the domain', () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(true); - - getDeleteAction().simulate('click', simulatedClickEvent); - - expect(actions.deleteDomain).toHaveBeenCalledWith( - expect.objectContaining({ id: '1234' }) - ); - }); - - it('clicking the action and not confirming does not delete the engine', () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(false); - - getDeleteAction().simulate('click', simulatedClickEvent); - - expect(actions.deleteDomain).not.toHaveBeenCalled(); - }); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx deleted file mode 100644 index d9aa54e02a337..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/domains_table.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiBasicTableColumn, EuiBasicTable } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { FormattedNumber } from '@kbn/i18n-react'; - -import { DELETE_BUTTON_LABEL, MANAGE_BUTTON_LABEL } from '../../../../shared/constants'; -import { KibanaLogic } from '../../../../shared/kibana'; -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; -import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; -import { AppLogic } from '../../../app_logic'; -import { ENGINE_CRAWLER_DOMAIN_PATH } from '../../../routes'; -import { generateEnginePath } from '../../engine'; -import { CrawlerDomainsLogic } from '../crawler_domains_logic'; -import { CrawlerDomain } from '../types'; - -import { getDeleteDomainConfirmationMessage } from '../utils'; - -import { CustomFormattedTimestamp } from './custom_formatted_timestamp'; - -export const DomainsTable: React.FC = () => { - const { domains, meta, dataLoading } = useValues(CrawlerDomainsLogic); - const { fetchCrawlerDomainsData, onPaginate, deleteDomain } = useActions(CrawlerDomainsLogic); - - useEffect(() => { - fetchCrawlerDomainsData(); - }, [meta.page.current]); - - const { - myRole: { canManageEngineCrawler }, - } = useValues(AppLogic); - - const columns: Array> = [ - { - field: 'url', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.domainURL', - { - defaultMessage: 'Domain URL', - } - ), - render: (_, domain: CrawlerDomain) => ( - - {domain.url} - - ), - }, - { - field: 'lastCrawl', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.lastActivity', - { - defaultMessage: 'Last activity', - } - ), - render: (lastCrawl: CrawlerDomain['lastCrawl']) => - lastCrawl ? : '', - }, - { - field: 'documentCount', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.documents', - { - defaultMessage: 'Documents', - } - ), - render: (documentCount: CrawlerDomain['documentCount']) => ( - - ), - }, - ]; - - const actionsColumn: EuiBasicTableColumn = { - name: i18n.translate('xpack.enterpriseSearch.appSearch.crawler.domainsTable.column.actions', { - defaultMessage: 'Actions', - }), - actions: [ - { - name: MANAGE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.manage.buttonLabel', - { - defaultMessage: 'Manage this domain', - } - ), - type: 'icon', - icon: 'eye', - onClick: (domain) => - KibanaLogic.values.navigateToUrl( - generateEnginePath(ENGINE_CRAWLER_DOMAIN_PATH, { domainId: domain.id }) - ), - }, - { - name: DELETE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.buttonLabel', - { - defaultMessage: 'Delete this domain', - } - ), - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (domain) => { - if (window.confirm(getDeleteDomainConfirmationMessage(domain.url))) { - deleteDomain(domain); - } - }, - }, - ], - }; - - if (canManageEngineCrawler) { - columns.push(actionsColumn); - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx deleted file mode 100644 index c6a93a8197a76..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 { EuiFieldText } from '@elastic/eui'; - -import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { CrawlerDomain } from '../types'; - -import { EntryPointsTable } from './entry_points_table'; - -describe('EntryPointsTable', () => { - const engineName = 'my-engine'; - const entryPoints = [ - { id: '1', value: '/whatever' }, - { id: '2', value: '/foo' }, - ]; - const domain: CrawlerDomain = { - createdOn: '2018-01-01T00:00:00.000Z', - documentCount: 10, - id: '6113e1407a2f2e6f42489794', - url: 'https://www.elastic.co', - crawlRules: [], - entryPoints, - sitemaps: [], - deduplicationEnabled: true, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(GenericEndpointInlineEditableTable).exists()).toBe(true); - }); - - describe('the first and only column in the table', () => { - it('shows the value of an entry point', () => { - const entryPoint = { id: '1', value: '/whatever' }; - - const wrapper = shallow( - - ); - - const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); - const column = shallow(
{columns[0].render(entryPoint)}
); - expect(column.html()).toContain('/whatever'); - }); - - it('can show the value of an entry point as editable', () => { - const entryPoint = { id: '1', value: '/whatever' }; - const onChange = jest.fn(); - - const wrapper = shallow( - - ); - - const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); - const column = shallow( -
- {columns[0].editingRender(entryPoint, onChange, { isInvalid: false, isLoading: false })} -
- ); - - const textField = column.find(EuiFieldText); - expect(textField.props()).toEqual( - expect.objectContaining({ - value: '/whatever', - disabled: false, // It would be disabled if isLoading is true - isInvalid: false, - prepend: 'https://www.elastic.co', - }) - ); - - textField.simulate('change', { target: { value: '/foo' } }); - expect(onChange).toHaveBeenCalledWith('/foo'); - }); - }); - - describe('routes', () => { - it('can calculate an update and delete route correctly', () => { - const wrapper = shallow( - - ); - - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const entryPoint = { id: '1', value: '/whatever' }; - expect(table.prop('deleteRoute')(entryPoint)).toEqual( - '/internal/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/entry_points/1' - ); - expect(table.prop('updateRoute')(entryPoint)).toEqual( - '/internal/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/entry_points/1' - ); - }); - }); - - it('shows a no items message whem there are no entry points to show', () => { - const wrapper = shallow( - - ); - - const editNewItems = jest.fn(); - const table = wrapper.find(GenericEndpointInlineEditableTable); - const message = mountWithIntl(
{table.prop('noItemsMessage')!(editNewItems)}
); - expect(message.html()).toContain('There are no existing entry points.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx deleted file mode 100644 index d4bfdf77704b8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiFieldText, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../../shared/doc_links'; -import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; -import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; -import { ItemWithAnID } from '../../../../shared/tables/types'; -import { CrawlerDomain, EntryPoint } from '../types'; - -import { EntryPointsTableLogic } from './entry_points_table_logic'; - -interface EntryPointsTableProps { - domain: CrawlerDomain; - engineName: string; - items: EntryPoint[]; -} - -export const EntryPointsTable: React.FC = ({ - domain, - engineName, - items, -}) => { - const { onAdd, onDelete, onUpdate } = useActions(EntryPointsTableLogic); - const field = 'value'; - - const columns: Array> = [ - { - editingRender: (entryPoint, onChange, { isInvalid, isLoading }) => ( - onChange(e.target.value)} - disabled={isLoading} - isInvalid={isInvalid} - prepend={domain.url} - /> - ), - render: (entryPoint) => ( - - {domain.url} - {(entryPoint as EntryPoint)[field]} - - ), - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.urlTableHead', - { defaultMessage: 'URL' } - ), - field, - }, - ]; - - const entryPointsRoute = `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}/entry_points`; - - const getEntryPointRoute = (entryPoint: EntryPoint) => - `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}/entry_points/${entryPoint.id}`; - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.description', { - defaultMessage: - 'Include the most important URLs for your website here. Entry point URLs will be the first pages to be indexed and processed for links to other pages.', - })}{' '} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText', - { defaultMessage: 'Learn more about entry points.' } - )} - -

- } - instanceId="EntryPointsTable" - items={items} - lastItemWarning={i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.lastItemMessage', - { defaultMessage: 'The crawler requires at least one entry point.' } - )} - // Since canRemoveLastItem is false, the only time noItemsMessage would be displayed is if the last entry point was deleted via the API. - noItemsMessage={(editNewItem) => ( - <> - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageTitle', - { - defaultMessage: 'There are no existing entry points.', - } - )} -

-
- - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageLinkText', - { defaultMessage: 'Add an entry point' } - )} - - ), - }} - /> - - - - )} - addRoute={entryPointsRoute} - canRemoveLastItem={false} - deleteRoute={getEntryPointRoute} - updateRoute={getEntryPointRoute} - dataProperty="entry_points" - onAdd={onAdd} - onDelete={onDelete} - onUpdate={onUpdate} - title={i18n.translate('xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.title', { - defaultMessage: 'Entry points', - })} - disableReordering - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts deleted file mode 100644 index 7d6704b9abdb3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - */ - -jest.mock('../crawler_single_domain_logic', () => ({ - CrawlerSingleDomainLogic: { - actions: { - updateEntryPoints: jest.fn(), - }, - }, -})); - -import { LogicMounter, mockFlashMessageHelpers } from '../../../../__mocks__/kea_logic'; - -import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; - -import { EntryPointsTableLogic } from './entry_points_table_logic'; - -describe('EntryPointsTableLogic', () => { - const { mount } = new LogicMounter(EntryPointsTableLogic); - const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('listeners', () => { - describe('onAdd', () => { - it('should update the entry points for the current domain, and clear flash messages', () => { - const entryThatWasAdded = { id: '2', value: 'bar' }; - const updatedEntries = [ - { id: '1', value: 'foo' }, - { id: '2', value: 'bar' }, - ]; - mount(); - EntryPointsTableLogic.actions.onAdd(entryThatWasAdded, updatedEntries); - expect(CrawlerSingleDomainLogic.actions.updateEntryPoints).toHaveBeenCalledWith( - updatedEntries - ); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - - describe('onDelete', () => { - it('should update the entry points for the current domain, clear flash messages, and show a success toast', () => { - const entryThatWasDeleted = { id: '2', value: 'bar' }; - const updatedEntries = [{ id: '1', value: 'foo' }]; - mount(); - EntryPointsTableLogic.actions.onDelete(entryThatWasDeleted, updatedEntries); - expect(CrawlerSingleDomainLogic.actions.updateEntryPoints).toHaveBeenCalledWith( - updatedEntries - ); - expect(clearFlashMessages).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - }); - - describe('onUpdate', () => { - it('should update the entry points for the current domain, clear flash messages, and show a success toast', () => { - const entryThatWasUpdated = { id: '2', value: 'baz' }; - const updatedEntries = [ - { id: '1', value: 'foo' }, - { id: '2', value: 'baz' }, - ]; - mount(); - EntryPointsTableLogic.actions.onUpdate(entryThatWasUpdated, updatedEntries); - expect(CrawlerSingleDomainLogic.actions.updateEntryPoints).toHaveBeenCalledWith( - updatedEntries - ); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts deleted file mode 100644 index 2332a24ea8b74..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { clearFlashMessages, flashSuccessToast } from '../../../../shared/flash_messages'; - -import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; - -import { EntryPoint } from '../types'; - -interface EntryPointsTableValues { - dataLoading: boolean; -} - -interface EntryPointsTableActions { - onAdd( - entryPoint: EntryPoint, - entryPoints: EntryPoint[] - ): { entryPoint: EntryPoint; entryPoints: EntryPoint[] }; - onDelete( - entryPoint: EntryPoint, - entryPoints: EntryPoint[] - ): { entryPoint: EntryPoint; entryPoints: EntryPoint[] }; - onUpdate( - entryPoint: EntryPoint, - entryPoints: EntryPoint[] - ): { entryPoint: EntryPoint; entryPoints: EntryPoint[] }; -} - -export const EntryPointsTableLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'crawler', 'entry_points_table'], - actions: () => ({ - onAdd: (entryPoint, entryPoints) => ({ entryPoint, entryPoints }), - onDelete: (entryPoint, entryPoints) => ({ entryPoint, entryPoints }), - onUpdate: (entryPoint, entryPoints) => ({ entryPoint, entryPoints }), - }), - listeners: () => ({ - onAdd: ({ entryPoints }) => { - CrawlerSingleDomainLogic.actions.updateEntryPoints(entryPoints); - clearFlashMessages(); - }, - onDelete: ({ entryPoint, entryPoints }) => { - CrawlerSingleDomainLogic.actions.updateEntryPoints(entryPoints); - clearFlashMessages(); - flashSuccessToast(`Entry point "${entryPoint.value}" was removed.`); - }, - onUpdate: ({ entryPoints }) => { - CrawlerSingleDomainLogic.actions.updateEntryPoints(entryPoints); - clearFlashMessages(); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.test.tsx deleted file mode 100644 index 21a76734fb330..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/shallow_useeffect.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFieldNumber, - EuiForm, - EuiSelect, - EuiSwitch, -} from '@elastic/eui'; - -import { CrawlUnits } from '../../types'; - -import { AutomaticCrawlScheduler } from './automatic_crawl_scheduler'; - -const MOCK_ACTIONS = { - // AutomaticCrawlSchedulerLogic - fetchCrawlSchedule: jest.fn(), - setCrawlFrequency: jest.fn(), - setCrawlUnit: jest.fn(), - saveChanges: jest.fn(), - toggleCrawlAutomatically: jest.fn(), - // ManageCrawlsPopoverLogic - closePopover: jest.fn(), -}; - -const MOCK_VALUES = { - crawlAutomatically: false, - crawlFrequency: 7, - crawlUnit: CrawlUnits.days, - isSubmitting: false, -}; - -describe('AutomaticCrawlScheduler', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - - wrapper = shallow(); - }); - - it('calls fetchCrawlSchedule on component load', () => { - expect(MOCK_ACTIONS.fetchCrawlSchedule).toHaveBeenCalled(); - }); - - it('renders', () => { - expect(wrapper.find(EuiForm)).toHaveLength(1); - expect(wrapper.find(EuiFieldNumber)).toHaveLength(1); - expect(wrapper.find(EuiSelect)).toHaveLength(1); - }); - - it('saves changes on form submit', () => { - const preventDefault = jest.fn(); - wrapper.find(EuiForm).simulate('submit', { preventDefault }); - - expect(preventDefault).toHaveBeenCalled(); - expect(MOCK_ACTIONS.saveChanges).toHaveBeenCalled(); - }); - - it('contains a switch that toggles automatic crawling', () => { - wrapper.find(EuiSwitch).simulate('change'); - - expect(MOCK_ACTIONS.toggleCrawlAutomatically).toHaveBeenCalled(); - }); - - it('contains a number field that updates the crawl frequency', () => { - wrapper.find(EuiFieldNumber).simulate('change', { target: { value: '10' } }); - - expect(MOCK_ACTIONS.setCrawlFrequency).toHaveBeenCalledWith(10); - }); - - it('contains a select field that updates the crawl unit', () => { - wrapper.find(EuiSelect).simulate('change', { target: { value: CrawlUnits.weeks } }); - - expect(MOCK_ACTIONS.setCrawlUnit).toHaveBeenCalledWith(CrawlUnits.weeks); - }); - - it('contains a button to close the popover', () => { - expect(wrapper.find(EuiButtonEmpty).prop('onClick')).toEqual(MOCK_ACTIONS.closePopover); - }); - - it('contains a submit button', () => { - expect(wrapper.find(EuiButton).prop('type')).toEqual('submit'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx deleted file mode 100644 index cb0377a471b93..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFieldNumber, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiLink, - EuiPopoverFooter, - EuiSelect, - EuiSpacer, - EuiSwitch, - EuiText, - htmlIdGenerator, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { CANCEL_BUTTON_LABEL, SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; -import { - DAYS_UNIT_LABEL, - HOURS_UNIT_LABEL, - MONTHS_UNIT_LABEL, - WEEKS_UNIT_LABEL, -} from '../../../../../shared/constants/units'; -import { docLinks } from '../../../../../shared/doc_links'; - -import { CrawlUnits } from '../../types'; - -import { AutomaticCrawlSchedulerLogic } from './automatic_crawl_scheduler_logic'; - -import { ManageCrawlsPopoverLogic } from './manage_crawls_popover_logic'; - -export const AutomaticCrawlScheduler: React.FC = () => { - const { - fetchCrawlSchedule, - setCrawlFrequency, - setCrawlUnit, - saveChanges, - toggleCrawlAutomatically, - } = useActions(AutomaticCrawlSchedulerLogic); - - const { closePopover } = useActions(ManageCrawlsPopoverLogic); - - const { crawlAutomatically, crawlFrequency, crawlUnit, isSubmitting } = useValues( - AutomaticCrawlSchedulerLogic - ); - - useEffect(() => { - fetchCrawlSchedule(); - }, []); - - const formId = htmlIdGenerator('AutomaticCrawlScheduler')(); - - return ( - { - event.preventDefault(); - saveChanges(); - }} - component="form" - id={formId} - > - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.readMoreLink', - { - defaultMessage: 'Read more.', - } - )} - - ), - }} - /> - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel', - { - defaultMessage: 'Crawl automatically', - } - )} - - } - onChange={toggleCrawlAutomatically} - compressed - /> - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix', - { - defaultMessage: 'Every', - } - )} - - - - setCrawlFrequency(parseInt(e.target.value, 10))} - /> - - - setCrawlUnit(e.target.value as CrawlUnits)} - /> - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.scheduleDescription', - { - defaultMessage: 'The crawl schedule applies to every domain on this engine.', - } - )} - - - - - - {CANCEL_BUTTON_LABEL} - - - - {SAVE_BUTTON_LABEL} - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts deleted file mode 100644 index 5c9e95e097dd4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/engine_logic.mock'; - -jest.mock('./manage_crawls_popover_logic', () => ({ - ManageCrawlsPopoverLogic: { - actions: { - closePopover: jest.fn(), - }, - }, -})); - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { CrawlUnits } from '../../types'; - -import { AutomaticCrawlSchedulerLogic } from './automatic_crawl_scheduler_logic'; -import { ManageCrawlsPopoverLogic } from './manage_crawls_popover_logic'; - -describe('AutomaticCrawlSchedulerLogic', () => { - const { mount } = new LogicMounter(AutomaticCrawlSchedulerLogic); - const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - - expect(AutomaticCrawlSchedulerLogic.values).toEqual({ - crawlAutomatically: false, - crawlFrequency: 7, - crawlUnit: CrawlUnits.days, - isSubmitting: false, - }); - }); - - describe('actions', () => { - describe('clearCrawlSchedule', () => { - it('sets crawl schedule related values to their defaults', () => { - mount({ - crawlAutomatically: true, - crawlFrequency: 36, - crawlUnit: CrawlUnits.hours, - }); - - AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule(); - - expect(AutomaticCrawlSchedulerLogic.values).toMatchObject({ - crawlAutomatically: false, - crawlFrequency: 7, - crawlUnit: CrawlUnits.days, - }); - }); - }); - - describe('toggleCrawlAutomatically', () => { - it('toggles the ability to crawl automatically', () => { - mount({ - crawlAutomatically: false, - }); - - AutomaticCrawlSchedulerLogic.actions.toggleCrawlAutomatically(); - - expect(AutomaticCrawlSchedulerLogic.values.crawlAutomatically).toEqual(true); - - AutomaticCrawlSchedulerLogic.actions.toggleCrawlAutomatically(); - - expect(AutomaticCrawlSchedulerLogic.values.crawlAutomatically).toEqual(false); - }); - }); - - describe('onDoneSubmitting', () => { - mount({ - isSubmitting: true, - }); - - AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting(); - - expect(AutomaticCrawlSchedulerLogic.values.isSubmitting).toEqual(false); - }); - - describe('setCrawlFrequency', () => { - it("sets the crawl schedule's frequency", () => { - mount({ - crawlFrequency: 36, - }); - - AutomaticCrawlSchedulerLogic.actions.setCrawlFrequency(12); - - expect(AutomaticCrawlSchedulerLogic.values.crawlFrequency).toEqual(12); - }); - }); - - describe('setCrawlSchedule', () => { - it("sets the crawl schedule's frequency and unit, and enables crawling automatically", () => { - mount(); - - AutomaticCrawlSchedulerLogic.actions.setCrawlSchedule({ - frequency: 3, - unit: CrawlUnits.hours, - }); - - expect(AutomaticCrawlSchedulerLogic.values).toMatchObject({ - crawlAutomatically: true, - crawlFrequency: 3, - crawlUnit: CrawlUnits.hours, - }); - }); - }); - - describe('setCrawlUnit', () => { - it("sets the crawl schedule's unit", () => { - mount({ - crawlUnit: CrawlUnits.months, - }); - - AutomaticCrawlSchedulerLogic.actions.setCrawlUnit(CrawlUnits.weeks); - - expect(AutomaticCrawlSchedulerLogic.values.crawlUnit).toEqual(CrawlUnits.weeks); - }); - }); - }); - - describe('listeners', () => { - describe('deleteCrawlSchedule', () => { - it('resets the states of the crawl scheduler and popover, and shows a toast, on success', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'clearCrawlSchedule'); - jest.spyOn(ManageCrawlsPopoverLogic.actions, 'closePopover'); - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); - http.delete.mockReturnValueOnce(Promise.resolve()); - - AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalledWith(expect.any(String)); - expect(ManageCrawlsPopoverLogic.actions.closePopover).toHaveBeenCalled(); - expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); - }); - - describe('error paths', () => { - it('resets the states of the crawl scheduler and popover on a 404 respose', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'clearCrawlSchedule'); - jest.spyOn(ManageCrawlsPopoverLogic.actions, 'closePopover'); - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); - http.delete.mockReturnValueOnce( - Promise.reject({ - response: { status: 404 }, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule).toHaveBeenCalled(); - expect(ManageCrawlsPopoverLogic.actions.closePopover).toHaveBeenCalled(); - expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); - }); - - it('flashes an error on a non-404 respose', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); - http.delete.mockReturnValueOnce( - Promise.reject({ - response: { status: 500 }, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith({ - response: { status: 500 }, - }); - expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); - }); - }); - }); - - describe('fetchCrawlSchedule', () => { - it('set the state of the crawl scheduler on success', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'setCrawlSchedule'); - http.get.mockReturnValueOnce( - Promise.resolve({ - unit: CrawlUnits.days, - frequency: '30', - }) - ); - - AutomaticCrawlSchedulerLogic.actions.fetchCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.setCrawlSchedule).toHaveBeenCalledWith({ - unit: CrawlUnits.days, - frequency: '30', - }); - }); - - describe('error paths', () => { - it('resets the states of the crawl scheduler on a 404 respose', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'clearCrawlSchedule'); - http.get.mockReturnValueOnce( - Promise.reject({ - response: { status: 404 }, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.fetchCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule).toHaveBeenCalled(); - }); - - it('flashes an error on a non-404 respose', async () => { - http.get.mockReturnValueOnce( - Promise.reject({ - response: { status: 500 }, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.fetchCrawlSchedule(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith({ - response: { status: 500 }, - }); - }); - }); - }); - - describe('saveChanges', () => { - it('updates or creates a crawl schedule if the user has chosen to crawl automatically', () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'submitCrawlSchedule'); - mount({ - crawlAutomatically: true, - }); - - AutomaticCrawlSchedulerLogic.actions.saveChanges(); - - expect(AutomaticCrawlSchedulerLogic.actions.submitCrawlSchedule); - }); - - it('deletes the crawl schedule if the user has chosen to disable automatic crawling', () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'deleteCrawlSchedule'); - mount({ - crawlAutomatically: false, - }); - - AutomaticCrawlSchedulerLogic.actions.saveChanges(); - - expect(AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule); - }); - }); - - describe('submitCrawlSchedule', () => { - it('sets the states of the crawl scheduler and closes the popover on success', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'setCrawlSchedule'); - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); - http.put.mockReturnValueOnce( - Promise.resolve({ - unit: CrawlUnits.days, - frequency: 30, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.submitCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.setCrawlSchedule).toHaveBeenCalledWith({ - unit: CrawlUnits.days, - frequency: 30, - }); - expect(ManageCrawlsPopoverLogic.actions.closePopover).toHaveBeenCalled(); - expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); - }); - - it('flashes an error callout if there is an error', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); - http.delete.mockReturnValueOnce( - Promise.reject({ - response: { status: 500 }, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith({ - response: { status: 500 }, - }); - expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.ts deleted file mode 100644 index 8c485a3e7d0e0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../shared/http'; -import { EngineLogic } from '../../../engine'; -import { CrawlSchedule, CrawlUnits } from '../../types'; - -import { ManageCrawlsPopoverLogic } from './manage_crawls_popover_logic'; - -export interface AutomaticCrawlSchedulerLogicValues { - crawlAutomatically: boolean; - crawlFrequency: CrawlSchedule['frequency']; - crawlUnit: CrawlSchedule['unit']; - isSubmitting: boolean; -} - -const DEFAULT_VALUES: Pick = { - crawlFrequency: 7, - crawlUnit: CrawlUnits.days, -}; - -export interface AutomaticCrawlSchedulerLogicActions { - clearCrawlSchedule(): void; - deleteCrawlSchedule(): void; - disableCrawlAutomatically(): void; - onDoneSubmitting(): void; - enableCrawlAutomatically(): void; - fetchCrawlSchedule(): void; - saveChanges(): void; - setCrawlFrequency(crawlFrequency: CrawlSchedule['frequency']): { - crawlFrequency: CrawlSchedule['frequency']; - }; - setCrawlSchedule(crawlSchedule: CrawlSchedule): { crawlSchedule: CrawlSchedule }; - setCrawlUnit(crawlUnit: CrawlSchedule['unit']): { crawlUnit: CrawlSchedule['unit'] }; - submitCrawlSchedule(): void; - toggleCrawlAutomatically(): void; -} - -export const AutomaticCrawlSchedulerLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'crawler', 'automatic_crawl_scheduler'], - actions: () => ({ - clearCrawlSchedule: true, - deleteCrawlSchedule: true, - disableCrawlAutomatically: true, - onDoneSubmitting: true, - enableCrawlAutomatically: true, - fetchCrawlSchedule: true, - saveChanges: true, - setCrawlSchedule: (crawlSchedule: CrawlSchedule) => ({ crawlSchedule }), - submitCrawlSchedule: true, - setCrawlFrequency: (crawlFrequency: string) => ({ crawlFrequency }), - setCrawlUnit: (crawlUnit: CrawlUnits) => ({ crawlUnit }), - toggleCrawlAutomatically: true, - }), - reducers: () => ({ - crawlAutomatically: [ - false, - { - clearCrawlSchedule: () => false, - setCrawlSchedule: () => true, - // @ts-expect-error upgrade typescript v5.1.6 - toggleCrawlAutomatically: (crawlAutomatically) => !crawlAutomatically, - }, - ], - crawlFrequency: [ - DEFAULT_VALUES.crawlFrequency, - { - clearCrawlSchedule: () => DEFAULT_VALUES.crawlFrequency, - // @ts-expect-error upgrade typescript v5.1.6 - setCrawlSchedule: (_, { crawlSchedule: { frequency } }) => frequency, - // @ts-expect-error upgrade typescript v5.1.6 - setCrawlFrequency: (_, { crawlFrequency }) => crawlFrequency, - }, - ], - crawlUnit: [ - DEFAULT_VALUES.crawlUnit, - { - clearCrawlSchedule: () => DEFAULT_VALUES.crawlUnit, - // @ts-expect-error upgrade typescript v5.1.6 - setCrawlSchedule: (_, { crawlSchedule: { unit } }) => unit, - // @ts-expect-error upgrade typescript v5.1.6 - setCrawlUnit: (_, { crawlUnit }) => crawlUnit, - }, - ], - isSubmitting: [ - false, - { - deleteCrawlSchedule: () => true, - onDoneSubmitting: () => false, - submitCrawlSchedule: () => true, - }, - ], - }), - listeners: ({ actions, values }) => ({ - deleteCrawlSchedule: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { closePopover } = ManageCrawlsPopoverLogic.actions; - - try { - await http.delete(`/internal/app_search/engines/${engineName}/crawler/crawl_schedule`); - actions.clearCrawlSchedule(); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage', - { - defaultMessage: 'Automatic crawling has been disabled.', - } - ) - ); - closePopover(); - } catch (e) { - // A 404 is expected and means the user has no crawl schedule to delete - if (e.response?.status === 404) { - actions.clearCrawlSchedule(); - closePopover(); - } else { - flashAPIErrors(e); - // Keep the popover open - } - } finally { - actions.onDoneSubmitting(); - } - }, - fetchCrawlSchedule: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const crawlSchedule: CrawlSchedule = await http.get( - `/internal/app_search/engines/${engineName}/crawler/crawl_schedule` - ); - actions.setCrawlSchedule(crawlSchedule); - } catch (e) { - // A 404 is expected and means the user does not have crawl schedule - // for this engine. We continue to use the defaults. - if (e.response.status === 404) { - actions.clearCrawlSchedule(); - } else { - flashAPIErrors(e); - } - } - }, - saveChanges: () => { - if (values.crawlAutomatically) { - actions.submitCrawlSchedule(); - } else { - actions.deleteCrawlSchedule(); - } - }, - submitCrawlSchedule: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { closePopover } = ManageCrawlsPopoverLogic.actions; - - try { - const crawlSchedule: CrawlSchedule = await http.put( - `/internal/app_search/engines/${engineName}/crawler/crawl_schedule`, - { - body: JSON.stringify({ - unit: values.crawlUnit, - frequency: values.crawlFrequency, - }), - } - ); - actions.setCrawlSchedule(crawlSchedule); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage', - { - defaultMessage: 'Your automatic crawling schedule has been updated.', - } - ) - ); - closePopover(); - } catch (e) { - flashAPIErrors(e); - } finally { - actions.onDoneSubmitting(); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover.test.tsx deleted file mode 100644 index db6063c2d6bd8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { ReactWrapper, shallow } from 'enzyme'; - -import { - EuiButton, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiPopover, - EuiResizeObserver, -} from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; -import { CrawlerDomain } from '../../types'; - -import { AutomaticCrawlScheduler } from './automatic_crawl_scheduler'; -import { ManageCrawlsPopover } from './manage_crawls_popover'; - -const MOCK_ACTIONS = { - closePopover: jest.fn(), - reApplyCrawlRules: jest.fn(), - togglePopover: jest.fn(), -}; - -const MOCK_VALUES = { - isOpen: false, -}; - -const MOCK_DOMAIN = { url: 'https://elastic.co' } as CrawlerDomain; - -describe('ManageCrawlsPopover', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(EuiPopover)).toBe(true); - expect(wrapper.prop('closePopover')).toEqual(MOCK_ACTIONS.closePopover); - expect(wrapper.dive().find(EuiButton).prop('onClick')).toEqual(MOCK_ACTIONS.togglePopover); - }); - - it('is closed by default', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiContextMenuPanel)).toHaveLength(0); - }); - - describe('when open', () => { - let wrapper: ReactWrapper; - let menuItems: ReactWrapper; - - beforeEach(() => { - setMockValues({ - ...MOCK_VALUES, - isOpen: true, - }); - - wrapper = mountWithIntl(); - - menuItems = wrapper - .find(EuiContextMenuPanel) - .find(EuiResizeObserver) - .find(EuiContextMenuItem); - }); - - it('includes a button to reapply crawl rules', () => { - menuItems.at(0).simulate('click'); - expect(MOCK_ACTIONS.reApplyCrawlRules).toHaveBeenCalledWith(MOCK_DOMAIN); - }); - - it('includes a form to set a crawl schedule ', () => { - menuItems.at(1).simulate('click'); - - expect(wrapper.find(EuiContextMenuPanel).find(AutomaticCrawlScheduler)); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover.tsx deleted file mode 100644 index f76c793776858..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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, EuiContextMenu, EuiPopover } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { CrawlerDomain } from '../../types'; - -import { AutomaticCrawlScheduler } from './automatic_crawl_scheduler'; - -import { ManageCrawlsPopoverLogic } from './manage_crawls_popover_logic'; - -interface ManageCrawlsPopoverProps { - domain?: CrawlerDomain; -} - -export const ManageCrawlsPopover: React.FC = ({ domain }) => { - const { closePopover, reApplyCrawlRules, togglePopover } = useActions(ManageCrawlsPopoverLogic); - - const { isOpen } = useValues(ManageCrawlsPopoverLogic); - - const panels = [ - { - id: 0, - items: [ - { - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRulesButtonLabel', - { defaultMessage: 'Re-apply crawl rules' } - ), - icon: 'refresh', - onClick: () => reApplyCrawlRules(domain), - }, - { - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingButtonLabel', - { defaultMessage: 'Automatic crawling' } - ), - icon: 'gear', - panel: 1, - }, - ], - }, - { - id: 1, - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.automaticCrawlingTitle', - { defaultMessage: 'Automatic crawling' } - ), - width: 400, - content: , - }, - ]; - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.manageCrawlsButtonLabel', - { defaultMessage: 'Manage crawls' } - )} - - } - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts deleted file mode 100644 index da879192efdfa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { CrawlerDomain } from '../../types'; - -import { ManageCrawlsPopoverLogic } from './manage_crawls_popover_logic'; - -describe('ManageCrawlsPopoverLogic', () => { - const { mount } = new LogicMounter(ManageCrawlsPopoverLogic); - const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - - expect(ManageCrawlsPopoverLogic.values).toEqual({ - isOpen: false, - }); - }); - - describe('actions', () => { - describe('closePopover', () => { - it('closes the popover', () => { - mount({ - isOpen: true, - }); - - ManageCrawlsPopoverLogic.actions.closePopover(); - - expect(ManageCrawlsPopoverLogic.values.isOpen).toEqual(false); - }); - }); - - describe('togglePopover', () => { - it('toggles the visibility of the popover', () => { - mount({ - isOpen: false, - }); - - ManageCrawlsPopoverLogic.actions.togglePopover(); - - expect(ManageCrawlsPopoverLogic.values.isOpen).toEqual(true); - - ManageCrawlsPopoverLogic.actions.togglePopover(); - - expect(ManageCrawlsPopoverLogic.values.isOpen).toEqual(false); - }); - }); - }); - - describe('listeners', () => { - describe('reApplyCrawlRules', () => { - it('flashes a success toast on success', async () => { - jest.spyOn(ManageCrawlsPopoverLogic.actions, 'closePopover'); - http.post.mockReturnValueOnce(Promise.resolve()); - - ManageCrawlsPopoverLogic.actions.reApplyCrawlRules({ - url: 'https://elastic.co', - } as CrawlerDomain); - await nextTick(); - - expect(flashSuccessToast).toHaveBeenCalledWith(expect.any(String)); - expect(ManageCrawlsPopoverLogic.actions.closePopover).toHaveBeenCalled(); - }); - - it('flashes an error callout if there is an error', async () => { - jest.spyOn(ManageCrawlsPopoverLogic.actions, 'closePopover'); - http.post.mockReturnValueOnce(Promise.reject('error')); - - ManageCrawlsPopoverLogic.actions.reApplyCrawlRules(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - expect(ManageCrawlsPopoverLogic.actions.closePopover).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.ts deleted file mode 100644 index f4567de366e02..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../shared/http'; -import { EngineLogic } from '../../../engine'; -import { CrawlerLogic } from '../../crawler_logic'; -import { CrawlerDomain } from '../../types'; - -export interface ManageCrawlsPopoverLogicValues { - isOpen: boolean; -} - -export interface ManageCrawlsPopoverLogicActions { - closePopover(): void; - reApplyCrawlRules(domain?: CrawlerDomain): { domain: CrawlerDomain }; - togglePopover(): void; -} - -export const ManageCrawlsPopoverLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'crawler', 'manage_crawls_popover'], - actions: () => ({ - closePopover: true, - reApplyCrawlRules: (domain) => ({ domain }), - togglePopover: true, - }), - reducers: () => ({ - isOpen: [ - false, - { - closePopover: () => false, - // @ts-expect-error upgrade typescript v5.1.6 - togglePopover: (currentIsOpen) => !currentIsOpen, - }, - ], - }), - listeners: ({ actions }) => ({ - reApplyCrawlRules: async ({ domain }) => { - const { engineName } = EngineLogic.values; - const { http } = HttpLogic.values; - const requestBody: { domains?: string[] } = {}; - - if (domain) { - requestBody.domains = [domain.url]; - } - - try { - await http.post(`/internal/app_search/engines/${engineName}/crawler/process_crawls`, { - body: JSON.stringify(requestBody), - }); - - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.manageCrawlsPopover.reApplyCrawlRules.successMessage', - { - defaultMessage: 'Crawl rules are being re-applied in the background', - } - ) - ); - - CrawlerLogic.actions.fetchCrawlerData(); - } catch (e) { - flashAPIErrors(e); - } finally { - actions.closePopover(); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/sitemaps_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/sitemaps_table.test.tsx deleted file mode 100644 index 418157d38e756..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/sitemaps_table.test.tsx +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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 { mockFlashMessageHelpers, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt, EuiFieldText } from '@elastic/eui'; - -import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { SitemapsTable } from './sitemaps_table'; - -describe('SitemapsTable', () => { - const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; - const engineName = 'my-engine'; - const sitemaps = [ - { id: '1', url: 'http://www.example.com/sitemap.xml' }, - { id: '2', url: 'http://www.example.com/whatever/sitemaps.xml' }, - ]; - const domain = { - crawlRules: [], - createdOn: '2018-01-01T00:00:00.000Z', - deduplicationEnabled: true, - entryPoints: [], - documentCount: 10, - id: '6113e1407a2f2e6f42489794', - url: 'https://www.elastic.co', - sitemaps, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(GenericEndpointInlineEditableTable).exists()).toBe(true); - }); - - describe('the first and only column in the table', () => { - it('shows the url of a sitemap', () => { - const sitemap = { id: '1', url: 'http://www.example.com/sitemap.xml' }; - - const wrapper = shallow( - - ); - - const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); - const column = shallow(
{columns[0].render(sitemap)}
); - expect(column.html()).toContain('http://www.example.com/sitemap.xml'); - }); - - it('can show the url of a sitemap as editable', () => { - const sitemap = { id: '1', url: 'http://www.example.com/sitemap.xml' }; - const onChange = jest.fn(); - - const wrapper = shallow( - - ); - - const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); - const column = shallow( -
- {columns[0].editingRender(sitemap, onChange, { isInvalid: false, isLoading: false })} -
- ); - - const textField = column.find(EuiFieldText); - expect(textField.props()).toEqual( - expect.objectContaining({ - value: 'http://www.example.com/sitemap.xml', - disabled: false, // It would be disabled if isLoading is true - isInvalid: false, - }) - ); - - textField.simulate('change', { target: { value: '/foo' } }); - expect(onChange).toHaveBeenCalledWith('/foo'); - }); - }); - - describe('routes', () => { - it('can calculate an update and delete route correctly', () => { - const wrapper = shallow( - - ); - - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const sitemap = { id: '1', url: '/whatever' }; - expect(table.prop('deleteRoute')(sitemap)).toEqual( - '/internal/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/sitemaps/1' - ); - expect(table.prop('updateRoute')(sitemap)).toEqual( - '/internal/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/sitemaps/1' - ); - }); - }); - - it('shows a no items message whem there are no sitemaps to show', () => { - const wrapper = shallow( - - ); - - const editNewItems = jest.fn(); - const table = wrapper.find(GenericEndpointInlineEditableTable); - const message = mountWithIntl(
{table.prop('noItemsMessage')!(editNewItems)}
); - expect(message.find(EuiEmptyPrompt).exists()).toBe(true); - }); - - describe('when a sitemap is added', () => { - it('should update the sitemaps for the current domain, and clear flash messages', () => { - const updateSitemaps = jest.fn(); - setMockActions({ - updateSitemaps, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const sitemapThatWasAdded = { id: '2', value: 'bar' }; - const updatedSitemaps = [ - { id: '1', value: 'foo' }, - { id: '2', value: 'bar' }, - ]; - table.prop('onAdd')(sitemapThatWasAdded, updatedSitemaps); - expect(updateSitemaps).toHaveBeenCalledWith(updatedSitemaps); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - - describe('when a sitemap is updated', () => { - it('should update the sitemaps for the current domain, and clear flash messages', () => { - const updateSitemaps = jest.fn(); - setMockActions({ - updateSitemaps, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const sitemapThatWasUpdated = { id: '2', value: 'bar' }; - const updatedSitemaps = [ - { id: '1', value: 'foo' }, - { id: '2', value: 'baz' }, - ]; - table.prop('onUpdate')(sitemapThatWasUpdated, updatedSitemaps); - expect(updateSitemaps).toHaveBeenCalledWith(updatedSitemaps); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - - describe('when a sitemap is deleted', () => { - it('should update the sitemaps for the current domain, clear flash messages, and show a success', () => { - const updateSitemaps = jest.fn(); - setMockActions({ - updateSitemaps, - }); - const wrapper = shallow( - - ); - const table = wrapper.find(GenericEndpointInlineEditableTable); - - const sitemapThatWasDeleted = { id: '2', value: 'bar' }; - const updatedSitemaps = [{ id: '1', value: 'foo' }]; - table.prop('onDelete')(sitemapThatWasDeleted, updatedSitemaps); - expect(updateSitemaps).toHaveBeenCalledWith(updatedSitemaps); - expect(clearFlashMessages).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/sitemaps_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/sitemaps_table.tsx deleted file mode 100644 index 8637fda449eba..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/sitemaps_table.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiButton, EuiEmptyPrompt, EuiFieldText, EuiText } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { clearFlashMessages, flashSuccessToast } from '../../../../shared/flash_messages'; -import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; -import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; -import { ItemWithAnID } from '../../../../shared/tables/types'; -import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; -import { CrawlerDomain, Sitemap } from '../types'; - -const ADD_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.addButtonLabel', - { defaultMessage: 'Add sitemap' } -); - -interface SitemapsTableProps { - domain: CrawlerDomain; - engineName: string; - items: Sitemap[]; -} - -export const SitemapsTable: React.FC = ({ domain, engineName, items }) => { - const { updateSitemaps } = useActions(CrawlerSingleDomainLogic); - const field = 'url'; - - const columns: Array> = [ - { - editingRender: (sitemap, onChange, { isInvalid, isLoading }) => ( - onChange(e.target.value)} - disabled={isLoading} - isInvalid={isInvalid} - /> - ), - render: (sitemap) => {(sitemap as Sitemap)[field]}, - name: i18n.translate('xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.urlTableHead', { - defaultMessage: 'URL', - }), - field, - }, - ]; - - const sitemapsRoute = `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}/sitemaps`; - const getSitemapRoute = (sitemap: Sitemap) => - `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}/sitemaps/${sitemap.id}`; - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.description', { - defaultMessage: 'Specify sitemap URLs for the crawler on this domain.', - })} -

- } - instanceId="SitemapsTable" - items={items} - canRemoveLastItem - noItemsMessage={(editNewItem) => ( - <> - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.emptyMessageTitle', - { - defaultMessage: 'There are no existing sitemaps.', - } - )} - - } - titleSize="s" - body={Add a sitemap to specify an entry point for the crawler.} - actions={{ADD_BUTTON_LABEL}} - /> - - )} - addRoute={sitemapsRoute} - deleteRoute={getSitemapRoute} - updateRoute={getSitemapRoute} - dataProperty="sitemaps" - onAdd={(_, newSitemaps) => { - updateSitemaps(newSitemaps as Sitemap[]); - clearFlashMessages(); - }} - onDelete={(_, newSitemaps) => { - updateSitemaps(newSitemaps as Sitemap[]); - clearFlashMessages(); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.deleteSuccessToastMessage', - { - defaultMessage: 'The sitemap has been deleted.', - } - ) - ); - }} - onUpdate={(_, newSitemaps) => { - updateSitemaps(newSitemaps as Sitemap[]); - clearFlashMessages(); - }} - title={i18n.translate('xpack.enterpriseSearch.appSearch.crawler.sitemapsTable.title', { - defaultMessage: 'Sitemaps', - })} - disableReordering - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/constants.ts deleted file mode 100644 index 7e95bf8fb8e45..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const CRAWLER_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.crawler.title', - { defaultMessage: 'Web Crawler' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts deleted file mode 100644 index f24fe4576e449..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { CrawlDetailLogic, CrawlDetailValues } from './crawl_detail_logic'; -import { CrawlType, CrawlerStatus, CrawlRequestWithDetailsFromServer } from './types'; -import { crawlRequestWithDetailsServerToClient } from './utils'; - -const DEFAULT_VALUES: CrawlDetailValues = { - dataLoading: true, - flyoutClosed: true, - crawlRequest: null, - crawlRequestFromServer: null, - selectedTab: 'preview', -}; - -const crawlRequestResponse: CrawlRequestWithDetailsFromServer = { - id: '12345', - status: CrawlerStatus.Pending, - created_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - began_at: null, - completed_at: null, - type: CrawlType.Full, - crawl_config: { - domain_allowlist: [], - seed_urls: [], - sitemap_urls: [], - max_crawl_depth: 10, - }, - stats: { - status: { - urls_allowed: 4, - pages_visited: 4, - crawl_duration_msec: 100, - avg_response_time_msec: 10, - status_codes: { - 200: 4, - 404: 0, - }, - }, - }, -}; - -const clientCrawlRequest = crawlRequestWithDetailsServerToClient(crawlRequestResponse); - -describe('CrawlDetailLogic', () => { - const { mount } = new LogicMounter(CrawlDetailLogic); - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(CrawlDetailLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('closeFlyout', () => { - it('closes the flyout', () => { - mount({ flyoutClosed: false }); - - CrawlDetailLogic.actions.closeFlyout(); - - expect(CrawlDetailLogic.values).toEqual({ - ...DEFAULT_VALUES, - flyoutClosed: true, - }); - }); - }); - - describe('onRecieveCrawlRequest', () => { - it('saves the crawl request and sets data loading to false', () => { - mount({ - dataLoading: true, - request: null, - }); - - CrawlDetailLogic.actions.onRecieveCrawlRequest(crawlRequestResponse); - - expect(CrawlDetailLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - crawlRequestFromServer: crawlRequestResponse, - crawlRequest: clientCrawlRequest, - }); - }); - }); - - describe('setSelectedTab', () => { - it('sets the select tab', () => { - mount({ - selectedTab: 'preview', - }); - - CrawlDetailLogic.actions.setSelectedTab('json'); - - expect(CrawlDetailLogic.values).toEqual({ - ...DEFAULT_VALUES, - selectedTab: 'json', - }); - }); - }); - - describe('openFlyout', () => { - it('opens the flyout and resets the selected tab', () => { - mount({ - flyoutClosed: true, - selectedTab: 'json', - }); - - CrawlDetailLogic.actions.openFlyout(); - - expect(CrawlDetailLogic.values).toEqual({ - ...DEFAULT_VALUES, - flyoutClosed: false, - selectedTab: 'preview', - }); - }); - }); - - describe('fetchCrawlRequest', () => { - it('sets loading to true', () => { - mount({ - dataLoading: false, - }); - - CrawlDetailLogic.actions.fetchCrawlRequest('12345'); - - expect(CrawlDetailLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('updates logic with data that has been converted from server to client', async () => { - mount(); - jest.spyOn(CrawlDetailLogic.actions, 'onRecieveCrawlRequest'); - - http.get.mockReturnValueOnce(Promise.resolve(crawlRequestResponse)); - - CrawlDetailLogic.actions.fetchCrawlRequest('12345'); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/crawl_requests/12345' - ); - expect(CrawlDetailLogic.actions.onRecieveCrawlRequest).toHaveBeenCalledWith( - crawlRequestResponse - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - CrawlDetailLogic.actions.fetchCrawlRequest('12345'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.ts deleted file mode 100644 index a6ed1e173854c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../shared/flash_messages'; - -import { HttpLogic } from '../../../shared/http'; -import { EngineLogic } from '../engine'; - -import { CrawlRequestWithDetails, CrawlRequestWithDetailsFromServer } from './types'; -import { crawlRequestWithDetailsServerToClient } from './utils'; - -type CrawlDetailFlyoutTabs = 'preview' | 'json'; - -export interface CrawlDetailValues { - crawlRequest: CrawlRequestWithDetails | null; - crawlRequestFromServer: CrawlRequestWithDetailsFromServer | null; - dataLoading: boolean; - flyoutClosed: boolean; - selectedTab: CrawlDetailFlyoutTabs; -} - -export interface CrawlDetailActions { - closeFlyout(): void; - fetchCrawlRequest(requestId: string): { requestId: string }; - onRecieveCrawlRequest(crawlRequestFromServer: CrawlRequestWithDetailsFromServer): { - crawlRequestFromServer: CrawlRequestWithDetailsFromServer; - }; - openFlyout(): void; - setSelectedTab(selectedTab: CrawlDetailFlyoutTabs): { selectedTab: CrawlDetailFlyoutTabs }; -} - -export const CrawlDetailLogic = kea>({ - path: ['enterprise_search', 'app_search', 'crawler', 'crawl_detail_logic'], - actions: { - closeFlyout: true, - fetchCrawlRequest: (requestId) => ({ requestId }), - onRecieveCrawlRequest: (crawlRequestFromServer) => ({ crawlRequestFromServer }), - openFlyout: true, - setSelectedTab: (selectedTab) => ({ selectedTab }), - }, - reducers: { - crawlRequest: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onRecieveCrawlRequest: (_, { crawlRequestFromServer }) => - crawlRequestWithDetailsServerToClient(crawlRequestFromServer), - }, - ], - crawlRequestFromServer: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onRecieveCrawlRequest: (_, { crawlRequestFromServer }) => crawlRequestFromServer, - }, - ], - dataLoading: [ - true, - { - fetchCrawlRequest: () => true, - onRecieveCrawlRequest: () => false, - }, - ], - flyoutClosed: [ - true, - { - openFlyout: () => false, - closeFlyout: () => true, - }, - ], - selectedTab: [ - 'preview', - { - // @ts-expect-error upgrade typescript v5.1.6 - openFlyout: () => 'preview', - // @ts-expect-error upgrade typescript v5.1.6 - setSelectedTab: (_, { selectedTab }) => selectedTab, - }, - ], - }, - listeners: ({ actions }) => ({ - fetchCrawlRequest: async ({ requestId }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/crawler/crawl_requests/${requestId}` - ); - - actions.onRecieveCrawlRequest(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts deleted file mode 100644 index a4963d7f21b51..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { Meta } from '../../../../../common/types'; - -import { DEFAULT_META } from '../../../shared/constants'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers/error_handling'; - -import { CrawlerDomainsLogic, CrawlerDomainsValues } from './crawler_domains_logic'; -import { CrawlerDataFromServer, CrawlerDomain, CrawlerDomainFromServer } from './types'; -import { crawlerDataServerToClient } from './utils'; - -const DEFAULT_VALUES: CrawlerDomainsValues = { - dataLoading: true, - domains: [], - meta: DEFAULT_META, -}; - -const crawlerDataResponse: CrawlerDataFromServer = { - domains: [ - { - id: '507f1f77bcf86cd799439011', - name: 'elastic.co', - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 13, - sitemaps: [], - entry_points: [], - crawl_rules: [], - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }, - ], - events: [], - most_recent_crawl_request: null, -}; - -const clientCrawlerData = crawlerDataServerToClient(crawlerDataResponse); - -const domainsFromServer: CrawlerDomainFromServer[] = [ - { - name: 'http://www.example.com', - created_on: 'foo', - document_count: 10, - id: '1', - crawl_rules: [], - entry_points: [], - sitemaps: [], - deduplication_enabled: true, - deduplication_fields: [], - available_deduplication_fields: [], - }, -]; - -const domains: CrawlerDomain[] = [ - { - createdOn: 'foo', - documentCount: 10, - id: '1', - url: 'http://www.example.com', - crawlRules: [], - entryPoints: [], - sitemaps: [], - deduplicationEnabled: true, - deduplicationFields: [], - availableDeduplicationFields: [], - }, -]; - -const meta: Meta = { - page: { - current: 2, - size: 100, - total_pages: 5, - total_results: 500, - }, -}; - -describe('CrawlerDomainsLogic', () => { - const { mount } = new LogicMounter(CrawlerDomainsLogic); - const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(CrawlerDomainsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onReceiveData', () => { - it('sets state from an API call', () => { - mount(); - - CrawlerDomainsLogic.actions.onReceiveData(domains, meta); - - expect(CrawlerDomainsLogic.values).toEqual({ - ...DEFAULT_VALUES, - domains, - meta, - dataLoading: false, - }); - }); - }); - - describe('onPaginate', () => { - it('sets dataLoading to true & sets meta state', () => { - mount({ dataLoading: false }); - CrawlerDomainsLogic.actions.onPaginate(5); - - expect(CrawlerDomainsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - meta: { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - current: 5, - }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('fetchCrawlerDomainsData', () => { - it('updates logic with data that has been converted from server to client', async () => { - mount(); - jest.spyOn(CrawlerDomainsLogic.actions, 'onReceiveData'); - - http.get.mockReturnValueOnce( - Promise.resolve({ - results: domainsFromServer, - meta, - }) - ); - - CrawlerDomainsLogic.actions.fetchCrawlerDomainsData(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/domains', - { - query: { 'page[current]': 1, 'page[size]': 10 }, - } - ); - expect(CrawlerDomainsLogic.actions.onReceiveData).toHaveBeenCalledWith(domains, meta); - }); - - it('displays any errors to the user', async () => { - mount(); - http.get.mockReturnValueOnce(Promise.reject('error')); - - CrawlerDomainsLogic.actions.fetchCrawlerDomainsData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - - describe('deleteDomain', () => { - it('deletes the domain and then calls crawlerDomainDeleted with the response', async () => { - jest.spyOn(CrawlerDomainsLogic.actions, 'crawlerDomainDeleted'); - http.delete.mockReturnValue(Promise.resolve(crawlerDataResponse)); - - CrawlerDomainsLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/domains/1234', - { - query: { respond_with: 'crawler_details' }, - } - ); - expect(CrawlerDomainsLogic.actions.crawlerDomainDeleted).toHaveBeenCalledWith( - clientCrawlerData - ); - }); - - itShowsServerErrorAsFlashMessage(http.delete, () => { - CrawlerDomainsLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.ts deleted file mode 100644 index 03f7b3e514a34..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; -import { DEFAULT_META } from '../../../shared/constants'; -import { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { updateMetaPageIndex } from '../../../shared/table_pagination'; -import { EngineLogic } from '../engine'; - -import { - CrawlerData, - CrawlerDataFromServer, - CrawlerDomain, - CrawlerDomainFromServer, -} from './types'; -import { crawlerDataServerToClient, crawlerDomainServerToClient } from './utils'; - -export interface CrawlerDomainsValues { - dataLoading: boolean; - domains: CrawlerDomain[]; - meta: Meta; -} - -interface CrawlerDomainsResponse { - results: CrawlerDomainFromServer[]; - meta: Meta; -} - -interface CrawlerDomainsActions { - deleteDomain(domain: CrawlerDomain): { domain: CrawlerDomain }; - fetchCrawlerDomainsData(): void; - onPaginate(newPageIndex: number): { newPageIndex: number }; - onReceiveData(domains: CrawlerDomain[], meta: Meta): { domains: CrawlerDomain[]; meta: Meta }; - crawlerDomainDeleted(data: CrawlerData): { data: CrawlerData }; -} - -export const CrawlerDomainsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'crawler', 'crawler_domains_logic'], - actions: { - deleteDomain: (domain) => ({ domain }), - fetchCrawlerDomainsData: true, - onReceiveData: (domains, meta) => ({ domains, meta }), - onPaginate: (newPageIndex) => ({ newPageIndex }), - crawlerDomainDeleted: (data) => ({ data }), - }, - reducers: { - dataLoading: [ - true, - { - onReceiveData: () => false, - onPaginate: () => true, - }, - ], - domains: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onReceiveData: (_, { domains }) => domains, - }, - ], - meta: [ - DEFAULT_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - onReceiveData: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - }, - listeners: ({ actions, values }) => ({ - fetchCrawlerDomainsData: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { meta } = values; - - const query = { - 'page[current]': meta.page.current, - 'page[size]': meta.page.size, - }; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/crawler/domains`, - { - query, - } - ); - - const domains = response.results.map(crawlerDomainServerToClient); - - actions.onReceiveData(domains, response.meta); - } catch (e) { - flashAPIErrors(e); - } - }, - - deleteDomain: async ({ domain }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.delete( - `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}`, - { - query: { - respond_with: 'crawler_details', - }, - } - ); - - const crawlerData = crawlerDataServerToClient(response); - // Publish for other logic files to listen for - actions.crawlerDomainDeleted(crawlerData); - actions.fetchCrawlerDomainsData(); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts deleted file mode 100644 index b3ac635c7867c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts +++ /dev/null @@ -1,426 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { CrawlerDomainsLogic } from './crawler_domains_logic'; -import { CrawlerLogic, CrawlerValues } from './crawler_logic'; -import { - CrawlerData, - CrawlerDataFromServer, - CrawlerPolicies, - CrawlerRules, - CrawlerStatus, - CrawlRule, - CrawlType, -} from './types'; -import { crawlerDataServerToClient } from './utils'; - -const DEFAULT_VALUES: CrawlerValues = { - events: [], - dataLoading: true, - domains: [], - mostRecentCrawlRequest: null, - mostRecentCrawlRequestStatus: CrawlerStatus.Success, - timeoutId: null, -}; - -const DEFAULT_CRAWL_RULE: CrawlRule = { - id: '-', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.regex, - pattern: '.*', -}; - -const MOCK_SERVER_CRAWLER_DATA: CrawlerDataFromServer = { - domains: [ - { - id: '507f1f77bcf86cd799439011', - name: 'elastic.co', - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 13, - sitemaps: [], - entry_points: [], - crawl_rules: [], - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }, - ], - events: [], - most_recent_crawl_request: null, -}; - -const MOCK_CLIENT_CRAWLER_DATA = crawlerDataServerToClient(MOCK_SERVER_CRAWLER_DATA); - -describe('CrawlerLogic', () => { - const { mount, unmount } = new LogicMounter(CrawlerLogic); - const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - jest.useFakeTimers({ legacyFakeTimers: true }); // this should be run before every test to reset these mocks - mount(); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - - it('has expected default values', () => { - expect(CrawlerLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('clearTimeoutId', () => { - it('clears the timeout in the logic', () => { - mount({ - timeoutId: setTimeout(() => {}, 1), - }); - - CrawlerLogic.actions.clearTimeoutId(); - - expect(CrawlerLogic.values.timeoutId).toEqual(null); - }); - }); - - describe('onCreateNewTimeout', () => { - it('sets the timeout in the logic', () => { - const timeout = setTimeout(() => {}, 1); - - CrawlerLogic.actions.onCreateNewTimeout(timeout); - - expect(CrawlerLogic.values.timeoutId).toEqual(timeout); - }); - }); - - describe('onReceiveCrawlerData', () => { - const crawlerData: CrawlerData = { - domains: [ - { - id: '507f1f77bcf86cd799439011', - createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000', - url: 'elastic.co', - documentCount: 13, - sitemaps: [], - entryPoints: [], - crawlRules: [], - defaultCrawlRule: DEFAULT_CRAWL_RULE, - deduplicationEnabled: false, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }, - ], - events: [ - { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - stage: 'crawl', - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: ['elastic.co'], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, - }, - ], - mostRecentCrawlRequest: { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - }, - }; - - beforeEach(() => { - CrawlerLogic.actions.onReceiveCrawlerData(crawlerData); - }); - - it('should set all received data as top-level values', () => { - expect(CrawlerLogic.values.domains).toEqual(crawlerData.domains); - expect(CrawlerLogic.values.events).toEqual(crawlerData.events); - expect(CrawlerLogic.values.mostRecentCrawlRequest).toEqual( - crawlerData.mostRecentCrawlRequest - ); - }); - - it('should set dataLoading to false', () => { - expect(CrawlerLogic.values.dataLoading).toEqual(false); - }); - }); - }); - - describe('listeners', () => { - describe('CrawlerDomainsLogic.actionTypes.crawlerDomainDeleted', () => { - it('updates data in state when a domain is deleted', () => { - jest.spyOn(CrawlerLogic.actions, 'onReceiveCrawlerData'); - CrawlerDomainsLogic.actions.crawlerDomainDeleted(MOCK_CLIENT_CRAWLER_DATA); - expect(CrawlerLogic.actions.onReceiveCrawlerData).toHaveBeenCalledWith( - MOCK_CLIENT_CRAWLER_DATA - ); - }); - }); - - describe('fetchCrawlerData', () => { - it('updates logic with data that has been converted from server to client', async () => { - jest.spyOn(CrawlerLogic.actions, 'onReceiveCrawlerData'); - http.get.mockReturnValueOnce(Promise.resolve(MOCK_SERVER_CRAWLER_DATA)); - - CrawlerLogic.actions.fetchCrawlerData(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/crawler'); - expect(CrawlerLogic.actions.onReceiveCrawlerData).toHaveBeenCalledWith( - MOCK_CLIENT_CRAWLER_DATA - ); - }); - - it('creates a new timeout when there is an active process crawl', async () => { - jest.spyOn(CrawlerLogic.actions, 'createNewTimeoutForCrawlerData'); - http.get.mockReturnValueOnce( - Promise.resolve({ - ...MOCK_SERVER_CRAWLER_DATA, - most_recent_crawl_request: null, - events: [ - { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Running, - stage: 'process', - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - }, - ], - }) - ); - - CrawlerLogic.actions.fetchCrawlerData(); - await nextTick(); - - expect(CrawlerLogic.actions.createNewTimeoutForCrawlerData).toHaveBeenCalled(); - }); - - describe('on success', () => { - [ - CrawlerStatus.Pending, - CrawlerStatus.Starting, - CrawlerStatus.Running, - CrawlerStatus.Canceling, - ].forEach((status) => { - it(`creates a new timeout for most recent crawl request status ${status}`, async () => { - jest.spyOn(CrawlerLogic.actions, 'createNewTimeoutForCrawlerData'); - http.get.mockReturnValueOnce( - Promise.resolve({ - ...MOCK_SERVER_CRAWLER_DATA, - most_recent_crawl_request: { status }, - }) - ); - - CrawlerLogic.actions.fetchCrawlerData(); - await nextTick(); - - expect(CrawlerLogic.actions.createNewTimeoutForCrawlerData).toHaveBeenCalled(); - }); - }); - - [CrawlerStatus.Success, CrawlerStatus.Failed, CrawlerStatus.Canceled].forEach((status) => { - it(`clears the timeout and fetches data for status ${status}`, async () => { - jest.spyOn(CrawlerLogic.actions, 'clearTimeoutId'); - jest.spyOn(CrawlerLogic.actions, 'fetchCrawlerData'); - http.get.mockReturnValueOnce( - Promise.resolve({ - ...MOCK_SERVER_CRAWLER_DATA, - most_recent_crawl_request: { status }, - }) - ); - - CrawlerLogic.actions.fetchCrawlerData(); - await nextTick(); - - expect(CrawlerLogic.actions.clearTimeoutId).toHaveBeenCalled(); - expect(CrawlerLogic.actions.fetchCrawlerData).toHaveBeenCalled(); - }); - }); - - it('clears the timeout if no events are active', async () => { - jest.spyOn(CrawlerLogic.actions, 'clearTimeoutId'); - - http.get.mockReturnValueOnce( - Promise.resolve({ - ...MOCK_SERVER_CRAWLER_DATA, - events: [ - { - status: CrawlerStatus.Failed, - crawl_config: {}, - }, - ], - }) - ); - - CrawlerLogic.actions.fetchCrawlerData(); - await nextTick(); - - expect(CrawlerLogic.actions.clearTimeoutId).toHaveBeenCalled(); - }); - }); - - it('calls flashApiErrors when there is an error on the request for crawler data', async () => { - jest.spyOn(CrawlerLogic.actions, 'createNewTimeoutForCrawlerData'); - http.get.mockReturnValueOnce(Promise.reject('error')); - - CrawlerLogic.actions.fetchCrawlerData(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - expect(CrawlerLogic.actions.createNewTimeoutForCrawlerData).toHaveBeenCalled(); - }); - }); - - describe('startCrawl', () => { - describe('success path', () => { - it('creates a new crawl request, fetches latest crawler data, then marks the request complete', async () => { - jest.spyOn(CrawlerLogic.actions, 'fetchCrawlerData'); - jest.spyOn(CrawlerLogic.actions, 'onStartCrawlRequestComplete'); - http.post.mockReturnValueOnce(Promise.resolve()); - - CrawlerLogic.actions.startCrawl(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/crawl_requests', - { body: JSON.stringify({ overrides: {} }) } - ); - expect(CrawlerLogic.actions.fetchCrawlerData).toHaveBeenCalled(); - expect(CrawlerLogic.actions.onStartCrawlRequestComplete).toHaveBeenCalled(); - }); - }); - - itShowsServerErrorAsFlashMessage(http.post, () => { - CrawlerLogic.actions.startCrawl(); - }); - - it('marks the request complete even after an error', async () => { - jest.spyOn(CrawlerLogic.actions, 'onStartCrawlRequestComplete'); - http.post.mockReturnValueOnce(Promise.reject()); - - CrawlerLogic.actions.startCrawl(); - await nextTick(); - - expect(CrawlerLogic.actions.onStartCrawlRequestComplete).toHaveBeenCalled(); - }); - }); - - describe('stopCrawl', () => { - describe('success path', () => { - it('stops the crawl starts and then fetches the latest crawler data', async () => { - jest.spyOn(CrawlerLogic.actions, 'fetchCrawlerData'); - http.post.mockReturnValueOnce(Promise.resolve()); - - CrawlerLogic.actions.stopCrawl(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/crawl_requests/cancel' - ); - expect(CrawlerLogic.actions.fetchCrawlerData).toHaveBeenCalled(); - }); - }); - - itShowsServerErrorAsFlashMessage(http.post, () => { - CrawlerLogic.actions.stopCrawl(); - }); - }); - - describe('createNewTimeoutForCrawlerData', () => { - it('saves the timeout ID in the logic', () => { - jest.spyOn(CrawlerLogic.actions, 'onCreateNewTimeout'); - jest.spyOn(CrawlerLogic.actions, 'fetchCrawlerData'); - - CrawlerLogic.actions.createNewTimeoutForCrawlerData(2000); - - expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 2000); - expect(CrawlerLogic.actions.onCreateNewTimeout).toHaveBeenCalled(); - - jest.runAllTimers(); - - expect(CrawlerLogic.actions.fetchCrawlerData).toHaveBeenCalled(); - }); - - it('clears a timeout if one already exists', () => { - const timeoutId = setTimeout(() => {}, 1); - mount({ - timeoutId, - }); - - CrawlerLogic.actions.createNewTimeoutForCrawlerData(2000); - - expect(clearTimeout).toHaveBeenCalledWith(timeoutId); - }); - }); - }); - - describe('selectors', () => { - describe('mostRecentCrawlRequestStatus', () => { - it('is Success when there is no recent crawl request', () => { - mount({ - mostRecentCrawlRequest: null, - }); - - expect(CrawlerLogic.values.mostRecentCrawlRequestStatus).toEqual(CrawlerStatus.Success); - }); - - it('is the most recent crawl request status', () => { - mount({ - mostRecentCrawlRequest: { - id: '2', - status: CrawlerStatus.Failed, - createdAt: 'Mon, 30 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - }, - }); - - expect(CrawlerLogic.values.mostRecentCrawlRequestStatus).toEqual(CrawlerStatus.Failed); - }); - }); - }); - - describe('events', () => { - describe('beforeUnmount', () => { - it('clears the timeout if there is one', () => { - jest.spyOn(global, 'setTimeout'); - - mount({ - timeoutId: setTimeout(() => {}, 1), - }); - unmount(); - - expect(setTimeout).toHaveBeenCalled(); - }); - - it('does not crash if no timeout exists', () => { - mount({ timeoutId: null }); - unmount(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts deleted file mode 100644 index ed2126ecd0757..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../shared/flash_messages'; - -import { HttpLogic } from '../../../shared/http'; -import { EngineLogic } from '../engine'; - -import { CrawlerDomainsLogic } from './crawler_domains_logic'; - -import { - CrawlerData, - CrawlerDomain, - CrawlEvent, - CrawlRequest, - CrawlerStatus, - CrawlerDataFromServer, -} from './types'; -import { crawlerDataServerToClient } from './utils'; - -const POLLING_DURATION = 1000; -const POLLING_DURATION_ON_FAILURE = 5000; -const ACTIVE_STATUSES = [ - CrawlerStatus.Pending, - CrawlerStatus.Starting, - CrawlerStatus.Running, - CrawlerStatus.Canceling, -]; - -export interface CrawlRequestOverrides { - domain_allowlist?: string[]; - max_crawl_depth?: number; - seed_urls?: string[]; - sitemap_urls?: string[]; - sitemap_discovery_disabled?: boolean; -} - -export interface CrawlerValues { - events: CrawlEvent[]; - dataLoading: boolean; - domains: CrawlerDomain[]; - mostRecentCrawlRequest: CrawlRequest | null; - mostRecentCrawlRequestStatus: CrawlerStatus | null; - timeoutId: NodeJS.Timeout | null; -} - -interface CrawlerActions { - clearTimeoutId(): void; - createNewTimeoutForCrawlerData(duration: number): { duration: number }; - fetchCrawlerData(): void; - onCreateNewTimeout(timeoutId: NodeJS.Timeout): { timeoutId: NodeJS.Timeout }; - onReceiveCrawlerData(data: CrawlerData): { data: CrawlerData }; - onStartCrawlRequestComplete(): void; - startCrawl(overrides?: CrawlRequestOverrides): { overrides?: CrawlRequestOverrides }; - stopCrawl(): void; -} - -export const CrawlerLogic = kea>({ - path: ['enterprise_search', 'app_search', 'crawler_logic'], - actions: { - clearTimeoutId: true, - createNewTimeoutForCrawlerData: (duration) => ({ duration }), - fetchCrawlerData: true, - onCreateNewTimeout: (timeoutId) => ({ timeoutId }), - onReceiveCrawlerData: (data) => ({ data }), - onStartCrawlRequestComplete: true, - startCrawl: (overrides) => ({ overrides }), - stopCrawl: () => null, - }, - reducers: { - dataLoading: [ - true, - { - onReceiveCrawlerData: () => false, - }, - ], - domains: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onReceiveCrawlerData: (_, { data: { domains } }) => domains, - }, - ], - events: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onReceiveCrawlerData: (_, { data: { events } }) => events, - }, - ], - mostRecentCrawlRequest: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onReceiveCrawlerData: (_, { data: { mostRecentCrawlRequest } }) => mostRecentCrawlRequest, - }, - ], - timeoutId: [ - null, - { - clearTimeoutId: () => null, - // @ts-expect-error upgrade typescript v5.1.6 - onCreateNewTimeout: (_, { timeoutId }) => timeoutId, - }, - ], - }, - selectors: ({ selectors }) => ({ - mostRecentCrawlRequestStatus: [ - () => [selectors.mostRecentCrawlRequest], - (crawlRequest: CrawlerValues['mostRecentCrawlRequest']) => { - if (crawlRequest) { - return crawlRequest.status; - } - return CrawlerStatus.Success; - }, - ], - }), - listeners: ({ actions, values }) => ({ - fetchCrawlerData: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/crawler` - ); - - const crawlerData = crawlerDataServerToClient(response); - actions.onReceiveCrawlerData(crawlerData); - - const continuePoll = - (crawlerData.mostRecentCrawlRequest && - ACTIVE_STATUSES.includes(crawlerData.mostRecentCrawlRequest.status)) || - crawlerData.events.find((event) => ACTIVE_STATUSES.includes(event.status)); - - if (continuePoll) { - actions.createNewTimeoutForCrawlerData(POLLING_DURATION); - } else { - actions.clearTimeoutId(); - } - } catch (e) { - flashAPIErrors(e); - actions.createNewTimeoutForCrawlerData(POLLING_DURATION_ON_FAILURE); - } - }, - startCrawl: async ({ overrides = {} }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - await http.post(`/internal/app_search/engines/${engineName}/crawler/crawl_requests`, { - body: JSON.stringify({ overrides }), - }); - actions.fetchCrawlerData(); - } catch (e) { - flashAPIErrors(e); - } finally { - actions.onStartCrawlRequestComplete(); - } - }, - stopCrawl: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - await http.post(`/internal/app_search/engines/${engineName}/crawler/crawl_requests/cancel`); - actions.fetchCrawlerData(); - } catch (e) { - flashAPIErrors(e); - } - }, - createNewTimeoutForCrawlerData: ({ duration }) => { - if (values.timeoutId) { - clearTimeout(values.timeoutId); - } - - const timeoutIdId = setTimeout(() => { - actions.fetchCrawlerData(); - }, duration); - - actions.onCreateNewTimeout(timeoutIdId); - }, - [CrawlerDomainsLogic.actionTypes.crawlerDomainDeleted]: ({ data }) => { - actions.onReceiveCrawlerData(data); - }, - }), - events: ({ values }) => ({ - beforeUnmount: () => { - if (values.timeoutId) { - clearTimeout(values.timeoutId); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx deleted file mode 100644 index 509346542ae13..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.test.tsx +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { useValues } from 'kea'; - -import { getPageHeaderActions } from '../../../test_helpers'; - -import { AddDomainFlyout } from './components/add_domain/add_domain_flyout'; -import { AddDomainForm } from './components/add_domain/add_domain_form'; -import { AddDomainFormErrors } from './components/add_domain/add_domain_form_errors'; -import { AddDomainFormSubmitButton } from './components/add_domain/add_domain_form_submit_button'; -import { AddDomainLogic } from './components/add_domain/add_domain_logic'; -import { CrawlDetailsFlyout } from './components/crawl_details_flyout'; -import { CrawlRequestsTable } from './components/crawl_requests_table'; -import { CrawlSelectDomainsModal } from './components/crawl_select_domains_modal/crawl_select_domains_modal'; -import { CrawlerStatusBanner } from './components/crawler_status_banner'; -import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator'; -import { DomainsTable } from './components/domains_table'; -import { ManageCrawlsPopover } from './components/manage_crawls_popover/manage_crawls_popover'; -import { CrawlerLogic } from './crawler_logic'; -import { CrawlerOverview } from './crawler_overview'; -import { - CrawlerDomainFromServer, - CrawlerPolicies, - CrawlerRules, - CrawlerStatus, - CrawlEventFromServer, - CrawlType, -} from './types'; - -const domains: CrawlerDomainFromServer[] = [ - { - id: 'x', - name: 'moviedatabase.com', - document_count: 13, - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - sitemaps: [], - entry_points: [], - crawl_rules: [], - default_crawl_rule: { - id: '-', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.regex, - pattern: '.*', - }, - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }, - { - id: 'y', - name: 'swiftype.com', - last_visited_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 40, - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - sitemaps: [], - entry_points: [], - crawl_rules: [], - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }, -]; - -const events: CrawlEventFromServer[] = [ - { - id: 'a', - stage: 'crawl', - status: CrawlerStatus.Canceled, - created_at: 'Mon, 31 Aug 2020 11:00:00 +0000', - began_at: 'Mon, 31 Aug 2020 12:00:00 +0000', - completed_at: 'Mon, 31 Aug 2020 13:00:00 +0000', - type: CrawlType.Full, - crawl_config: { - domain_allowlist: ['moviedatabase.com', 'swiftype.com'], - seed_urls: [], - sitemap_urls: [], - max_crawl_depth: 10, - }, - }, - { - id: 'b', - stage: 'crawl', - status: CrawlerStatus.Success, - created_at: 'Mon, 31 Aug 2020 14:00:00 +0000', - began_at: 'Mon, 31 Aug 2020 15:00:00 +0000', - completed_at: 'Mon, 31 Aug 2020 16:00:00 +0000', - type: CrawlType.Partial, - crawl_config: { - domain_allowlist: ['swiftype.com'], - seed_urls: [], - sitemap_urls: [], - max_crawl_depth: 10, - }, - }, -]; - -describe('CrawlerOverview', () => { - const mockValues = { - dataLoading: false, - domains, - events, - mostRecentCrawlRequest: null, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('contains a crawler status banner', () => { - setMockValues(mockValues); - - const wrapper = shallow(); - - expect(wrapper.find(CrawlerStatusBanner)).toHaveLength(1); - }); - - it('contains a crawler status indicator', () => { - setMockValues(mockValues); - - const wrapper = shallow(); - - expect(getPageHeaderActions(wrapper).find(CrawlerStatusIndicator)).toHaveLength(1); - }); - - it('contains a popover to manage crawls', () => { - setMockValues(mockValues); - - const wrapper = shallow(); - - expect(getPageHeaderActions(wrapper).find(ManageCrawlsPopover)).toHaveLength(1); - }); - - it('hides the domain and crawl request tables when there are no domains, and no crawl requests', () => { - setMockValues({ ...mockValues, domains: [], events: [] }); - - const wrapper = shallow(); - - expect(wrapper.find(AddDomainForm)).toHaveLength(1); - expect(wrapper.find(AddDomainFormSubmitButton)).toHaveLength(1); - expect(wrapper.find(AddDomainFlyout)).toHaveLength(0); - expect(wrapper.find(DomainsTable)).toHaveLength(0); - expect(wrapper.find(CrawlRequestsTable)).toHaveLength(0); - }); - - it('shows the domain and the crawl request tables when there are domains, but no crawl requests', () => { - setMockValues({ ...mockValues, events: [] }); - - const wrapper = shallow(); - - expect(wrapper.find(AddDomainForm)).toHaveLength(0); - expect(wrapper.find(AddDomainFormSubmitButton)).toHaveLength(0); - expect(wrapper.find(AddDomainFlyout)).toHaveLength(1); - expect(wrapper.find(DomainsTable)).toHaveLength(1); - expect(wrapper.find(CrawlRequestsTable)).toHaveLength(1); - }); - - it('hides the domain table and shows the crawl request tables when there are crawl requests but no domains', () => { - setMockValues({ ...mockValues, domains: [] }); - - const wrapper = shallow(); - - expect(wrapper.find(AddDomainForm)).toHaveLength(1); - expect(wrapper.find(AddDomainFormSubmitButton)).toHaveLength(1); - expect(wrapper.find(AddDomainFlyout)).toHaveLength(0); - expect(wrapper.find(DomainsTable)).toHaveLength(0); - expect(wrapper.find(CrawlRequestsTable)).toHaveLength(1); - }); - - it('shows the domain and the crawl request tables when there are crawl requests and domains', () => { - setMockValues(mockValues); - - const wrapper = shallow(); - - expect(wrapper.find(AddDomainForm)).toHaveLength(0); - expect(wrapper.find(AddDomainFormSubmitButton)).toHaveLength(0); - expect(wrapper.find(AddDomainFlyout)).toHaveLength(1); - expect(wrapper.find(DomainsTable)).toHaveLength(1); - expect(wrapper.find(CrawlRequestsTable)).toHaveLength(1); - }); - - it('contains a crawl details flyout', () => { - setMockValues(mockValues); - - const wrapper = shallow(); - - expect(wrapper.find(CrawlDetailsFlyout)).toHaveLength(1); - }); - - it('contains a AddDomainFormErrors when there are errors', () => { - const errors = ['Domain name already exists']; - - (useValues as jest.Mock).mockImplementation((logic) => { - switch (logic) { - case AddDomainLogic: - return { errors }; - case CrawlerLogic: - return { ...mockValues, domains: [], events: [] }; - default: - return {}; - } - }); - - const wrapper = shallow(); - - expect(wrapper.find(AddDomainFormErrors)).toHaveLength(1); - }); - - it('contains a modal to start a crawl with selected domains', () => { - const wrapper = shallow(); - - expect(wrapper.find(CrawlSelectDomainsModal)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx deleted file mode 100644 index d18f40c4c9f23..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../shared/doc_links'; -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { AddDomainFlyout } from './components/add_domain/add_domain_flyout'; -import { AddDomainForm } from './components/add_domain/add_domain_form'; -import { AddDomainFormErrors } from './components/add_domain/add_domain_form_errors'; -import { AddDomainFormSubmitButton } from './components/add_domain/add_domain_form_submit_button'; -import { AddDomainLogic } from './components/add_domain/add_domain_logic'; -import { CrawlCustomSettingsFlyout } from './components/crawl_custom_settings_flyout/crawl_custom_settings_flyout'; -import { CrawlDetailsFlyout } from './components/crawl_details_flyout'; -import { CrawlRequestsTable } from './components/crawl_requests_table'; -import { CrawlSelectDomainsModal } from './components/crawl_select_domains_modal/crawl_select_domains_modal'; -import { CrawlerStatusBanner } from './components/crawler_status_banner'; -import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator'; -import { DomainsTable } from './components/domains_table'; -import { ManageCrawlsPopover } from './components/manage_crawls_popover/manage_crawls_popover'; -import { CRAWLER_TITLE } from './constants'; -import { CrawlerLogic } from './crawler_logic'; - -export const CrawlerOverview: React.FC = () => { - const { events, dataLoading, domains } = useValues(CrawlerLogic); - const { errors: addDomainErrors } = useValues(AddDomainLogic); - - return ( - , ], - }} - isLoading={dataLoading} - > - - - {domains.length > 0 ? ( - <> - - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.domainsTitle', { - defaultMessage: 'Domains', - })} -

-
-
- - - -
- - - - ) : ( - <> - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.empty.title', { - defaultMessage: 'Add a domain to get started', - })} -

-
- -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.empty.description', { - defaultMessage: - "Easily index your website's content. To get started, enter your domain name, provide optional entry points and crawl rules, and we will handle the rest.", - })}{' '} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.empty.crawlerDocumentationLinkDescription', - { - defaultMessage: 'Learn more about the web crawler', - } - )} - -

-
- {addDomainErrors && ( - <> - - - - )} - - - - - - - - - - )} - {(events.length > 0 || domains.length > 0) && ( - <> - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTitle', { - defaultMessage: 'Recent crawl requests', - })} -

-
- - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.crawlRequestsDescription', { - defaultMessage: - "Recent crawl requests are logged here. Using the request ID of each crawl, you can track progress and examine crawl events in Kibana's Discover or Logs user interfaces.", - })}{' '} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.configurationDocumentationLinkDescription', - { - defaultMessage: 'Learn more about configuring crawler logs in Kibana', - } - )} - -

-
- - - - )} - - - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_router.test.tsx deleted file mode 100644 index da2b3cf2261b7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_router.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { setMockActions } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { CrawlerOverview } from './crawler_overview'; -import { CrawlerRouter } from './crawler_router'; -import { CrawlerSingleDomain } from './crawler_single_domain'; - -describe('CrawlerRouter', () => { - const mockActions = { - fetchCrawlerData: jest.fn(), - }; - - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(mockActions); - wrapper = shallow(); - }); - - it('calls fetchCrawlerData and starts polling on page load', () => { - expect(mockActions.fetchCrawlerData).toHaveBeenCalledTimes(1); - }); - - it('renders a crawler views', () => { - expect(wrapper.find(CrawlerOverview)).toHaveLength(1); - expect(wrapper.find(CrawlerSingleDomain)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_router.tsx deleted file mode 100644 index 48d6b95de675b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_router.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions } from 'kea'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { ENGINE_CRAWLER_DOMAIN_PATH, ENGINE_CRAWLER_PATH } from '../../routes'; - -import { CrawlerLogic } from './crawler_logic'; - -import { CrawlerOverview } from './crawler_overview'; -import { CrawlerSingleDomain } from './crawler_single_domain'; - -export const CrawlerRouter: React.FC = () => { - const { fetchCrawlerData } = useActions(CrawlerLogic); - - useEffect(() => { - fetchCrawlerData(); - }, []); - - return ( - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.test.tsx deleted file mode 100644 index addf4093a167b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; -import { mockUseParams } from '../../../__mocks__/react_router'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { getPageHeaderActions } from '../../../test_helpers'; - -import { CrawlSelectDomainsModal } from './components/crawl_select_domains_modal/crawl_select_domains_modal'; -import { CrawlerStatusBanner } from './components/crawler_status_banner'; -import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator'; -import { DeduplicationPanel } from './components/deduplication_panel'; -import { DeleteDomainPanel } from './components/delete_domain_panel'; -import { ManageCrawlsPopover } from './components/manage_crawls_popover/manage_crawls_popover'; -import { CrawlerSingleDomain } from './crawler_single_domain'; - -const MOCK_VALUES = { - // CrawlerSingleDomainLogic - dataLoading: false, - domain: { - url: 'https://elastic.co', - }, -}; - -const MOCK_ACTIONS = { - fetchCrawlerData: jest.fn(), - fetchDomainData: jest.fn(), -}; - -describe('CrawlerSingleDomain', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUseParams.mockReturnValue({ domainId: '507f1f77bcf86cd799439011' }); - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(DeleteDomainPanel)).toHaveLength(1); - expect(wrapper.prop('pageHeader').pageTitle).toEqual('https://elastic.co'); - }); - - it('does not render a page header and uses placeholder chrome while loading', () => { - setMockValues({ - ...MOCK_VALUES, - dataLoading: true, - domain: null, - }); - - const wrapper = shallow(); - - expect(wrapper.prop('pageChrome')).toContain('...'); - expect(wrapper.prop('pageHeader')).toBeUndefined(); - }); - - it('contains a crawler status banner', () => { - const wrapper = shallow(); - - expect(wrapper.find(CrawlerStatusBanner)).toHaveLength(1); - }); - - it('contains a crawler status indicator', () => { - const wrapper = shallow(); - - expect(getPageHeaderActions(wrapper).find(CrawlerStatusIndicator)).toHaveLength(1); - }); - - it('contains a popover to manage crawls', () => { - const wrapper = shallow(); - - expect(getPageHeaderActions(wrapper).find(ManageCrawlsPopover)).toHaveLength(1); - }); - - it('contains a panel to manage deduplication settings', () => { - const wrapper = shallow(); - - expect(wrapper.find(DeduplicationPanel)).toHaveLength(1); - }); - - it('contains a panel to delete the domain', () => { - const wrapper = shallow(); - - expect(wrapper.find(DeleteDomainPanel)).toHaveLength(1); - }); - - it('contains a modal to start a crawl with selected domains', () => { - const wrapper = shallow(); - - expect(wrapper.find(CrawlSelectDomainsModal)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx deleted file mode 100644 index 6baa1c9aee10c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useParams } from 'react-router-dom'; - -import { useActions, useValues } from 'kea'; - -import { EuiPanel, EuiSpacer } from '@elastic/eui'; - -import { EngineLogic, getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { CrawlCustomSettingsFlyout } from './components/crawl_custom_settings_flyout/crawl_custom_settings_flyout'; -import { CrawlRulesTable } from './components/crawl_rules_table'; -import { CrawlSelectDomainsModal } from './components/crawl_select_domains_modal/crawl_select_domains_modal'; -import { CrawlerStatusBanner } from './components/crawler_status_banner'; -import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator'; -import { DeduplicationPanel } from './components/deduplication_panel'; -import { DeleteDomainPanel } from './components/delete_domain_panel'; -import { EntryPointsTable } from './components/entry_points_table'; -import { ManageCrawlsPopover } from './components/manage_crawls_popover/manage_crawls_popover'; -import { SitemapsTable } from './components/sitemaps_table'; -import { CRAWLER_TITLE } from './constants'; -import { CrawlerSingleDomainLogic } from './crawler_single_domain_logic'; - -export const CrawlerSingleDomain: React.FC = () => { - const { domainId } = useParams() as { domainId: string }; - const { engineName } = EngineLogic.values; - - const { dataLoading, domain } = useValues(CrawlerSingleDomainLogic); - - const { fetchDomainData } = useActions(CrawlerSingleDomainLogic); - - useEffect(() => { - fetchDomainData(domainId); - }, []); - - return ( - , ], - } - } - isLoading={dataLoading} - > - - - {domain && ( - <> - - - - - - - - - - - - - - )} - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts deleted file mode 100644 index 53b63831f14b7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts +++ /dev/null @@ -1,275 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, - mockKibanaValues, -} from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -jest.mock('./crawler_logic', () => ({ - CrawlerLogic: { - actions: { - fetchCrawlerData: jest.fn(), - }, - }, -})); - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { CrawlerLogic } from './crawler_logic'; -import { CrawlerSingleDomainLogic, CrawlerSingleDomainValues } from './crawler_single_domain_logic'; -import { CrawlerDomain, CrawlerPolicies, CrawlerRules } from './types'; - -const DEFAULT_VALUES: CrawlerSingleDomainValues = { - dataLoading: true, - domain: null, -}; - -describe('CrawlerSingleDomainLogic', () => { - const { mount } = new LogicMounter(CrawlerSingleDomainLogic); - const { http } = mockHttpValues; - const { flashSuccessToast } = mockFlashMessageHelpers; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(CrawlerSingleDomainLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onReceiveDomainData', () => { - const domain = { - id: '507f1f77bcf86cd799439011', - }; - - beforeEach(() => { - CrawlerSingleDomainLogic.actions.onReceiveDomainData(domain as CrawlerDomain); - }); - - it('should set the domain', () => { - expect(CrawlerSingleDomainLogic.values.domain).toEqual(domain); - }); - }); - - describe('updateEntryPoints', () => { - beforeEach(() => { - mount({ - domain: { - id: '507f1f77bcf86cd799439011', - entryPoints: [], - }, - }); - - CrawlerSingleDomainLogic.actions.updateEntryPoints([ - { - id: '1234', - value: '/', - }, - ]); - }); - - it('should update the entry points on the domain', () => { - expect(CrawlerSingleDomainLogic.values.domain).toEqual({ - id: '507f1f77bcf86cd799439011', - entryPoints: [ - { - id: '1234', - value: '/', - }, - ], - }); - }); - }); - - describe('updateSitemaps', () => { - beforeEach(() => { - mount({ - domain: { - id: '507f1f77bcf86cd799439011', - sitemaps: [], - }, - }); - - CrawlerSingleDomainLogic.actions.updateSitemaps([ - { - id: '1234', - url: 'http://www.example.com/sitemap.xml', - }, - ]); - }); - - it('should update the sitemaps on the domain', () => { - expect(CrawlerSingleDomainLogic.values.domain).toEqual({ - id: '507f1f77bcf86cd799439011', - sitemaps: [ - { - id: '1234', - url: 'http://www.example.com/sitemap.xml', - }, - ], - }); - }); - }); - - describe('updateCrawlRules', () => { - beforeEach(() => { - mount({ - domain: { - id: '507f1f77bcf86cd799439011', - crawlRules: [], - }, - }); - - CrawlerSingleDomainLogic.actions.updateCrawlRules([ - { - id: '1234', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.beginsWith, - pattern: 'foo', - }, - ]); - }); - - it('should update the crawl rules on the domain', () => { - expect(CrawlerSingleDomainLogic.values.domain).toEqual({ - id: '507f1f77bcf86cd799439011', - crawlRules: [ - { - id: '1234', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.beginsWith, - pattern: 'foo', - }, - ], - }); - }); - }); - }); - - describe('listeners', () => { - describe('deleteDomain', () => { - it('flashes a success toast and redirects the user to the crawler overview on success', async () => { - jest.spyOn(CrawlerLogic.actions, 'fetchCrawlerData'); - const { navigateToUrl } = mockKibanaValues; - - http.delete.mockReturnValue(Promise.resolve()); - - CrawlerSingleDomainLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/domains/1234' - ); - - expect(CrawlerLogic.actions.fetchCrawlerData).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/crawler'); - }); - - itShowsServerErrorAsFlashMessage(http.delete, () => { - CrawlerSingleDomainLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain); - }); - }); - - describe('fetchDomainData', () => { - it('updates logic with data that has been converted from server to client', async () => { - jest.spyOn(CrawlerSingleDomainLogic.actions, 'onReceiveDomainData'); - http.get.mockReturnValueOnce( - Promise.resolve({ - id: '507f1f77bcf86cd799439011', - name: 'https://elastic.co', - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 13, - sitemaps: [], - entry_points: [], - crawl_rules: [], - }) - ); - - CrawlerSingleDomainLogic.actions.fetchDomainData('507f1f77bcf86cd799439011'); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/domains/507f1f77bcf86cd799439011' - ); - expect(CrawlerSingleDomainLogic.actions.onReceiveDomainData).toHaveBeenCalledWith({ - id: '507f1f77bcf86cd799439011', - createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000', - url: 'https://elastic.co', - documentCount: 13, - sitemaps: [], - entryPoints: [], - crawlRules: [], - }); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - CrawlerSingleDomainLogic.actions.fetchDomainData('507f1f77bcf86cd799439011'); - }); - }); - - describe('submitDeduplicationUpdate', () => { - it('updates logic with data that has been converted from server to client', async () => { - jest.spyOn(CrawlerSingleDomainLogic.actions, 'onReceiveDomainData'); - http.put.mockReturnValueOnce( - Promise.resolve({ - id: '507f1f77bcf86cd799439011', - name: 'https://elastic.co', - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 13, - sitemaps: [], - entry_points: [], - crawl_rules: [], - deduplication_enabled: true, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }) - ); - - CrawlerSingleDomainLogic.actions.submitDeduplicationUpdate( - { id: '507f1f77bcf86cd799439011' } as CrawlerDomain, - { fields: ['title'], enabled: true } - ); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/crawler/domains/507f1f77bcf86cd799439011', - { - body: JSON.stringify({ deduplication_enabled: true, deduplication_fields: ['title'] }), - } - ); - expect(CrawlerSingleDomainLogic.actions.onReceiveDomainData).toHaveBeenCalledWith({ - id: '507f1f77bcf86cd799439011', - createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000', - url: 'https://elastic.co', - documentCount: 13, - sitemaps: [], - entryPoints: [], - crawlRules: [], - deduplicationEnabled: true, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - CrawlerSingleDomainLogic.actions.submitDeduplicationUpdate( - { id: '507f1f77bcf86cd799439011' } as CrawlerDomain, - { fields: ['title'], enabled: true } - ); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts deleted file mode 100644 index f09a6adba63f6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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 { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; - -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; -import { ENGINE_CRAWLER_PATH } from '../../routes'; -import { EngineLogic, generateEnginePath } from '../engine'; - -import { CrawlerLogic } from './crawler_logic'; - -import { CrawlerDomain, EntryPoint, Sitemap, CrawlRule, CrawlerDomainFromServer } from './types'; -import { crawlerDomainServerToClient, getDeleteDomainSuccessMessage } from './utils'; - -export interface CrawlerSingleDomainValues { - dataLoading: boolean; - domain: CrawlerDomain | null; -} - -interface CrawlerSingleDomainActions { - deleteDomain(domain: CrawlerDomain): { domain: CrawlerDomain }; - fetchDomainData(domainId: string): { domainId: string }; - onReceiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain }; - updateCrawlRules(crawlRules: CrawlRule[]): { crawlRules: CrawlRule[] }; - updateEntryPoints(entryPoints: EntryPoint[]): { entryPoints: EntryPoint[] }; - updateSitemaps(entryPoints: Sitemap[]): { sitemaps: Sitemap[] }; - submitDeduplicationUpdate( - domain: CrawlerDomain, - payload: { fields?: string[]; enabled?: boolean } - ): { domain: CrawlerDomain; fields: string[]; enabled: boolean }; -} - -export const CrawlerSingleDomainLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'crawler', 'crawler_single_domain'], - actions: { - deleteDomain: (domain) => ({ domain }), - fetchDomainData: (domainId) => ({ domainId }), - onReceiveDomainData: (domain) => ({ domain }), - updateCrawlRules: (crawlRules) => ({ crawlRules }), - updateEntryPoints: (entryPoints) => ({ entryPoints }), - updateSitemaps: (sitemaps) => ({ sitemaps }), - submitDeduplicationUpdate: (domain, { fields, enabled }) => ({ domain, fields, enabled }), - }, - reducers: { - dataLoading: [ - true, - { - onReceiveDomainData: () => false, - }, - ], - domain: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onReceiveDomainData: (_, { domain }) => domain, - // @ts-expect-error upgrade typescript v5.1.6 - updateCrawlRules: (currentDomain, { crawlRules }) => - ({ ...currentDomain, crawlRules } as CrawlerDomain), - // @ts-expect-error upgrade typescript v5.1.6 - updateEntryPoints: (currentDomain, { entryPoints }) => - ({ ...currentDomain, entryPoints } as CrawlerDomain), - // @ts-expect-error upgrade typescript v5.1.6 - updateSitemaps: (currentDomain, { sitemaps }) => - ({ ...currentDomain, sitemaps } as CrawlerDomain), - }, - ], - }, - listeners: ({ actions }) => ({ - deleteDomain: async ({ domain }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - await http.delete( - `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}` - ); - - CrawlerLogic.actions.fetchCrawlerData(); - flashSuccessToast(getDeleteDomainSuccessMessage(domain.url)); - KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CRAWLER_PATH)); - } catch (e) { - flashAPIErrors(e); - } - }, - fetchDomainData: async ({ domainId }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/crawler/domains/${domainId}` - ); - - const domainData = crawlerDomainServerToClient(response); - - actions.onReceiveDomainData(domainData); - } catch (e) { - flashAPIErrors(e); - } - }, - submitDeduplicationUpdate: async ({ domain, fields, enabled }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const payload = { - deduplication_enabled: enabled, - deduplication_fields: fields, - }; - - try { - const response = await http.put( - `/internal/app_search/engines/${engineName}/crawler/domains/${domain.id}`, - { - body: JSON.stringify(payload), - } - ); - - const domainData = crawlerDomainServerToClient(response); - - actions.onReceiveDomainData(domainData); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/index.ts deleted file mode 100644 index 58fb0a7cebb1a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { CRAWLER_TITLE } from './constants'; -export { CrawlerRouter } from './crawler_router'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts deleted file mode 100644 index 9dc133cb682a7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/types.ts +++ /dev/null @@ -1,346 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export enum CrawlerPolicies { - allow = 'allow', - deny = 'deny', -} - -export const getReadableCrawlerPolicy = (policy: CrawlerPolicies) => { - switch (policy) { - case CrawlerPolicies.allow: - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.allowLabel', - { - defaultMessage: 'Allow', - } - ); - case CrawlerPolicies.deny: - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesPolicies.disallowLabel', - { - defaultMessage: 'Disallow', - } - ); - } -}; - -export enum CrawlerRules { - beginsWith = 'begins', - endsWith = 'ends', - contains = 'contains', - regex = 'regex', -} - -export const getReadableCrawlerRule = (rule: CrawlerRules) => { - switch (rule) { - case CrawlerRules.beginsWith: - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.beginsWithLabel', - { - defaultMessage: 'Begins with', - } - ); - case CrawlerRules.endsWith: - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.endsWithLabel', - { - defaultMessage: 'Ends with', - } - ); - case CrawlerRules.contains: - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.containsLabel', - { - defaultMessage: 'Contains', - } - ); - case CrawlerRules.regex: - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesCrawlerRules.regexLabel', - { - defaultMessage: 'Regex', - } - ); - } -}; - -export interface CrawlRule { - id: string; - policy: CrawlerPolicies; - rule: CrawlerRules; - pattern: string; -} - -export interface EntryPoint { - id: string; - value: string; -} - -export interface Sitemap { - id: string; - url: string; -} - -export interface CrawlerDomain { - createdOn: string; - documentCount: number; - id: string; - lastCrawl?: string; - url: string; - crawlRules: CrawlRule[]; - defaultCrawlRule?: CrawlRule; - entryPoints: EntryPoint[]; - sitemaps: Sitemap[]; - deduplicationEnabled: boolean; - deduplicationFields: string[]; - availableDeduplicationFields: string[]; -} - -export interface CrawlerDomainFromServer { - id: string; - name: string; - created_on: string; - last_visited_at?: string; - document_count: number; - crawl_rules: CrawlRule[]; - default_crawl_rule?: CrawlRule; - entry_points: EntryPoint[]; - sitemaps: Sitemap[]; - deduplication_enabled: boolean; - deduplication_fields: string[]; - available_deduplication_fields: string[]; -} - -export interface CrawlerData { - domains: CrawlerDomain[]; - events: CrawlEvent[]; - mostRecentCrawlRequest: CrawlRequest | null; -} - -export interface CrawlerDataFromServer { - domains: CrawlerDomainFromServer[]; - events: CrawlEventFromServer[]; - most_recent_crawl_request: CrawlRequestFromServer | null; -} - -export interface CrawlerDomainValidationResultFromServer { - valid: boolean; - results: Array<{ - name: string; - result: 'ok' | 'warning' | 'failure'; - comment: string; - }>; -} - -export type CrawlerDomainValidationStepState = '' | 'loading' | 'valid' | 'warning' | 'invalid'; - -export interface CrawlerDomainValidationStep { - state: CrawlerDomainValidationStepState; - blockingFailure?: boolean; - message?: string; -} - -interface CrawlerDomainValidationState { - initialValidation: CrawlerDomainValidationStep; - networkConnectivity: CrawlerDomainValidationStep; - indexingRestrictions: CrawlerDomainValidationStep; - contentVerification: CrawlerDomainValidationStep; -} - -export interface CrawlerDomainValidationResult { - steps: CrawlerDomainValidationState; -} - -export type CrawlerDomainValidationResultChange = Partial; - -export type CrawlerDomainValidationStepName = - | 'initialValidation' - | 'networkConnectivity' - | 'indexingRestrictions' - | 'contentVerification'; -// See SharedTogo::Crawler::Status for details on how these are generated -export enum CrawlerStatus { - Pending = 'pending', - Suspended = 'suspended', - Starting = 'starting', - Running = 'running', - Suspending = 'suspending', - Canceling = 'canceling', - Success = 'success', - Failed = 'failed', - Canceled = 'canceled', - Skipped = 'skipped', -} - -export enum CrawlType { - Full = 'full', - Partial = 'partial', -} -export interface CrawlRequestFromServer { - id: string; - status: CrawlerStatus; - created_at: string; - began_at: string | null; - completed_at: string | null; -} - -export interface CrawlRequest { - id: string; - status: CrawlerStatus; - createdAt: string; - beganAt: string | null; - completedAt: string | null; -} - -export interface CrawlRequestStats { - status: { - avgResponseTimeMSec?: number; - crawlDurationMSec?: number; - pagesVisited?: number; - urlsAllowed?: number; - statusCodes?: { - [code: string]: number; - }; - }; -} - -export interface CrawlRequestStatsFromServer { - status: { - avg_response_time_msec?: number; - crawl_duration_msec?: number; - pages_visited?: number; - urls_allowed?: number; - status_codes?: { - [code: string]: number; - }; - }; -} - -export interface CrawlConfig { - domainAllowlist: string[]; - seedUrls: string[]; - sitemapUrls: string[]; - maxCrawlDepth: number; -} - -export interface CrawlConfigFromServer { - domain_allowlist: string[]; - seed_urls: string[]; - sitemap_urls: string[]; - max_crawl_depth: number; -} - -export type CrawlRequestWithDetailsFromServer = CrawlRequestFromServer & { - type: CrawlType; - crawl_config: CrawlConfigFromServer; - stats: CrawlRequestStatsFromServer; -}; - -export type CrawlRequestWithDetails = CrawlRequest & { - type: CrawlType; - crawlConfig: CrawlConfig; - stats: CrawlRequestStats | null; -}; - -export type CrawlEventStage = 'crawl' | 'process'; - -export type CrawlEventFromServer = CrawlRequestFromServer & { - stage: CrawlEventStage; - type: CrawlType; - crawl_config: CrawlConfigFromServer; -}; - -export type CrawlEvent = CrawlRequest & { - stage: CrawlEventStage; - type: CrawlType; - crawlConfig: CrawlConfig; -}; - -export const readableCrawlerStatuses: { [key in CrawlerStatus]: string } = { - [CrawlerStatus.Pending]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.pending', - { defaultMessage: 'Pending' } - ), - [CrawlerStatus.Suspended]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspended', - { defaultMessage: 'Suspended' } - ), - [CrawlerStatus.Starting]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.starting', - { defaultMessage: 'Starting' } - ), - [CrawlerStatus.Running]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.running', - { defaultMessage: 'Running' } - ), - [CrawlerStatus.Suspending]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.suspending', - { defaultMessage: 'Suspending' } - ), - [CrawlerStatus.Canceling]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceling', - { defaultMessage: 'Canceling' } - ), - [CrawlerStatus.Success]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.success', - { defaultMessage: 'Success' } - ), - [CrawlerStatus.Failed]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.failed', - { defaultMessage: 'Failed' } - ), - [CrawlerStatus.Canceled]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.canceled', - { defaultMessage: 'Canceled' } - ), - [CrawlerStatus.Skipped]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlerStatusOptions.skipped', - { defaultMessage: 'Skipped' } - ), -}; - -export const readableCrawlTypes: { [key in CrawlType]: string } = { - [CrawlType.Full]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.full', - { defaultMessage: 'Full' } - ), - [CrawlType.Partial]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlTypeOptions.partial', - { defaultMessage: 'Partial' } - ), -}; - -export interface CrawlSchedule { - frequency: number; - unit: CrawlUnits; -} - -// The BE uses a singular form of each unit -// See shared_togo/app/models/shared_togo/crawler/crawl_schedule.rb -export enum CrawlUnits { - hours = 'hour', - days = 'day', - weeks = 'week', - months = 'month', -} - -export interface DomainConfigFromServer { - id: string; - name: string; - seed_urls: string[]; - sitemap_urls: string[]; -} - -export interface DomainConfig { - id: string; - name: string; - seedUrls: string[]; - sitemapUrls: string[]; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts deleted file mode 100644 index cded3b539c527..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.test.ts +++ /dev/null @@ -1,520 +0,0 @@ -/* - * 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 { - CrawlerPolicies, - CrawlerRules, - CrawlRule, - CrawlerDomainFromServer, - CrawlerDomainValidationStep, - CrawlerDomainValidationResultFromServer, - CrawlRequestFromServer, - CrawlerStatus, - CrawlerData, - CrawlRequest, - CrawlerDomain, - CrawlType, - CrawlRequestWithDetailsFromServer, - CrawlRequestWithDetails, - CrawlEvent, - CrawlEventFromServer, - CrawlRequestStatsFromServer, -} from './types'; - -import { - crawlerDomainServerToClient, - crawlerDataServerToClient, - crawlDomainValidationToResult, - crawlEventServerToClient, - crawlRequestServerToClient, - crawlRequestWithDetailsServerToClient, - getDeleteDomainConfirmationMessage, - getDeleteDomainSuccessMessage, - getCrawlRulePathPatternTooltip, - crawlRequestStatsServerToClient, - domainConfigServerToClient, -} from './utils'; - -const DEFAULT_CRAWL_RULE: CrawlRule = { - id: '-', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.regex, - pattern: '.*', -}; - -describe('crawlerDomainServerToClient', () => { - it('converts the API payload into properties matching our code style', () => { - const id = '507f1f77bcf86cd799439011'; - const name = 'moviedatabase.com'; - - const defaultServerPayload: CrawlerDomainFromServer = { - id, - name, - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 13, - sitemaps: [], - entry_points: [], - crawl_rules: [], - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }; - - const defaultClientPayload: CrawlerDomain = { - id, - createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000', - url: name, - documentCount: 13, - sitemaps: [], - entryPoints: [], - crawlRules: [], - deduplicationEnabled: false, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }; - - expect(crawlerDomainServerToClient(defaultServerPayload)).toStrictEqual(defaultClientPayload); - expect( - crawlerDomainServerToClient({ - ...defaultServerPayload, - last_visited_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, lastCrawl: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - expect( - crawlerDomainServerToClient({ - ...defaultServerPayload, - default_crawl_rule: DEFAULT_CRAWL_RULE, - }) - ).toStrictEqual({ ...defaultClientPayload, defaultCrawlRule: DEFAULT_CRAWL_RULE }); - }); -}); - -describe('crawlRequestServerToClient', () => { - it('converts the API payload into properties matching our code style', () => { - const id = '507f1f77bcf86cd799439011'; - - const defaultServerPayload: CrawlRequestFromServer = { - id, - status: CrawlerStatus.Pending, - created_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - began_at: null, - completed_at: null, - }; - - const defaultClientPayload: CrawlRequest = { - id, - status: CrawlerStatus.Pending, - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - }; - - expect(crawlRequestServerToClient(defaultServerPayload)).toStrictEqual(defaultClientPayload); - expect( - crawlRequestServerToClient({ - ...defaultServerPayload, - began_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, beganAt: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - expect( - crawlRequestServerToClient({ - ...defaultServerPayload, - completed_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, completedAt: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - }); -}); - -describe('crawlRequestStatsServerToClient', () => { - it('converts the API payload into properties matching our code style', () => { - const defaultServerPayload: CrawlRequestStatsFromServer = { - status: { - urls_allowed: 4, - pages_visited: 4, - crawl_duration_msec: 100, - avg_response_time_msec: 10, - status_codes: { - 200: 4, - 404: 0, - }, - }, - }; - - expect(crawlRequestStatsServerToClient(defaultServerPayload)).toEqual({ - status: { - urlsAllowed: 4, - pagesVisited: 4, - crawlDurationMSec: 100, - avgResponseTimeMSec: 10, - statusCodes: { - 200: 4, - 404: 0, - }, - }, - }); - }); -}); - -describe('crawlRequestWithDetailsServerToClient', () => { - it('converts the API payload into properties matching our code style', () => { - const id = '507f1f77bcf86cd799439011'; - - const defaultServerPayload: CrawlRequestWithDetailsFromServer = { - id, - status: CrawlerStatus.Pending, - created_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - began_at: null, - completed_at: null, - type: CrawlType.Full, - crawl_config: { - domain_allowlist: [], - seed_urls: [], - sitemap_urls: [], - max_crawl_depth: 10, - }, - stats: { - status: { - urls_allowed: 4, - pages_visited: 4, - crawl_duration_msec: 100, - avg_response_time_msec: 10, - status_codes: { - 200: 4, - 404: 0, - }, - }, - }, - }; - - const defaultClientPayload: CrawlRequestWithDetails = { - id, - status: CrawlerStatus.Pending, - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: [], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, - stats: { - status: { - urlsAllowed: 4, - pagesVisited: 4, - crawlDurationMSec: 100, - avgResponseTimeMSec: 10, - statusCodes: { - 200: 4, - 404: 0, - }, - }, - }, - }; - - expect(crawlRequestWithDetailsServerToClient(defaultServerPayload)).toStrictEqual( - defaultClientPayload - ); - expect( - crawlRequestWithDetailsServerToClient({ - ...defaultServerPayload, - began_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, beganAt: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - expect( - crawlRequestWithDetailsServerToClient({ - ...defaultServerPayload, - completed_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, completedAt: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - }); -}); - -describe('crawlEventServerToClient', () => { - it('converts the API payload into properties matching our code style', () => { - const id = '507f1f77bcf86cd799439011'; - - const defaultServerPayload: CrawlEventFromServer = { - id, - status: CrawlerStatus.Pending, - created_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - began_at: null, - completed_at: null, - type: CrawlType.Full, - crawl_config: { - domain_allowlist: [], - seed_urls: [], - sitemap_urls: [], - max_crawl_depth: 10, - }, - stage: 'crawl', - }; - - const defaultClientPayload: CrawlEvent = { - id, - status: CrawlerStatus.Pending, - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: CrawlType.Full, - crawlConfig: { - domainAllowlist: [], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, - stage: 'crawl', - }; - - expect(crawlEventServerToClient(defaultServerPayload)).toStrictEqual(defaultClientPayload); - expect( - crawlEventServerToClient({ - ...defaultServerPayload, - began_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, beganAt: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - expect( - crawlEventServerToClient({ - ...defaultServerPayload, - completed_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - }) - ).toStrictEqual({ ...defaultClientPayload, completedAt: 'Mon, 31 Aug 2020 17:00:00 +0000' }); - }); -}); - -describe('crawlerDataServerToClient', () => { - let output: CrawlerData; - - const domains: CrawlerDomainFromServer[] = [ - { - id: 'x', - name: 'moviedatabase.com', - document_count: 13, - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - sitemaps: [], - entry_points: [], - crawl_rules: [], - default_crawl_rule: DEFAULT_CRAWL_RULE, - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }, - { - id: 'y', - name: 'swiftype.com', - last_visited_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - document_count: 40, - created_on: 'Mon, 31 Aug 2020 17:00:00 +0000', - sitemaps: [], - entry_points: [], - crawl_rules: [], - deduplication_enabled: false, - deduplication_fields: ['title'], - available_deduplication_fields: ['title', 'description'], - }, - ]; - - beforeAll(() => { - output = crawlerDataServerToClient({ - domains, - events: [ - { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - stage: 'crawl', - created_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - began_at: null, - completed_at: null, - type: CrawlType.Full, - crawl_config: { - domain_allowlist: ['https://www.elastic.co'], - seed_urls: [], - sitemap_urls: [], - max_crawl_depth: 10, - }, - }, - ], - most_recent_crawl_request: { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - created_at: 'Mon, 31 Aug 2020 17:00:00 +0000', - began_at: null, - completed_at: null, - }, - }); - }); - - it('converts all data from the server form to their client form', () => { - expect(output.domains).toEqual([ - { - id: 'x', - url: 'moviedatabase.com', - documentCount: 13, - createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000', - sitemaps: [], - entryPoints: [], - crawlRules: [], - defaultCrawlRule: DEFAULT_CRAWL_RULE, - deduplicationEnabled: false, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }, - { - id: 'y', - url: 'swiftype.com', - lastCrawl: 'Mon, 31 Aug 2020 17:00:00 +0000', - documentCount: 40, - createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000', - sitemaps: [], - entryPoints: [], - crawlRules: [], - deduplicationEnabled: false, - deduplicationFields: ['title'], - availableDeduplicationFields: ['title', 'description'], - }, - ]); - expect(output.events).toEqual([ - { - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - stage: 'crawl', - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - type: 'full', - crawlConfig: { - domainAllowlist: ['https://www.elastic.co'], - seedUrls: [], - sitemapUrls: [], - maxCrawlDepth: 10, - }, - }, - ]); - expect(output.mostRecentCrawlRequest).toEqual({ - id: '618d0e66abe97bc688328900', - status: CrawlerStatus.Pending, - createdAt: 'Mon, 31 Aug 2020 17:00:00 +0000', - beganAt: null, - completedAt: null, - }); - }); -}); - -describe('crawlDomainValidationToResult', () => { - it('handles results with warnings', () => { - const data: CrawlerDomainValidationResultFromServer = { - valid: true, - results: [ - { - name: '-', - result: 'warning', - comment: 'A warning, not failure', - }, - ], - }; - - expect(crawlDomainValidationToResult(data)).toEqual({ - blockingFailure: false, - state: 'warning', - message: 'A warning, not failure', - } as CrawlerDomainValidationStep); - }); - - it('handles valid results, without warnings', () => { - const data: CrawlerDomainValidationResultFromServer = { - valid: true, - results: [ - { - name: '-', - result: 'ok', - comment: 'Something happened', - }, - ], - }; - - expect(crawlDomainValidationToResult(data)).toEqual({ - state: 'valid', - } as CrawlerDomainValidationStep); - }); - - it('handes invalid results', () => { - const data: CrawlerDomainValidationResultFromServer = { - valid: false, - results: [ - { - name: '-', - result: 'failure', - comment: 'Something unexpected happened', - }, - ], - }; - - expect(crawlDomainValidationToResult(data)).toEqual({ - blockingFailure: true, - state: 'invalid', - message: 'Something unexpected happened', - } as CrawlerDomainValidationStep); - }); -}); - -describe('getDeleteDomainConfirmationMessage', () => { - it('includes the url', () => { - expect(getDeleteDomainConfirmationMessage('https://elastic.co/')).toContain( - 'https://elastic.co' - ); - }); -}); - -describe('getDeleteDomainSuccessMessage', () => { - it('includes the url', () => { - expect(getDeleteDomainSuccessMessage('https://elastic.co/')).toContain('https://elastic.co'); - }); -}); - -describe('getCrawlRulePathPatternTooltip', () => { - it('includes regular expression', () => { - const crawlRule: CrawlRule = { - id: '-', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.regex, - pattern: '.*', - }; - - expect(getCrawlRulePathPatternTooltip(crawlRule)).toContain('regular expression'); - }); - - it('includes meta', () => { - const crawlRule: CrawlRule = { - id: '-', - policy: CrawlerPolicies.allow, - rule: CrawlerRules.beginsWith, - pattern: '/elastic', - }; - - expect(getCrawlRulePathPatternTooltip(crawlRule)).not.toContain('regular expression'); - expect(getCrawlRulePathPatternTooltip(crawlRule)).toContain('meta'); - }); -}); - -describe('domainConfigServerToClient', () => { - it('converts the domain config payload into properties matching our code style', () => { - expect( - domainConfigServerToClient({ - id: '1234', - name: 'https://www.elastic.co', - seed_urls: [], - sitemap_urls: [], - }) - ).toEqual({ - id: '1234', - name: 'https://www.elastic.co', - seedUrls: [], - sitemapUrls: [], - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts deleted file mode 100644 index c97410f91a001..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/utils.ts +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { - CrawlerDomain, - CrawlerDomainFromServer, - CrawlerData, - CrawlerDataFromServer, - CrawlerDomainValidationResultFromServer, - CrawlerDomainValidationStep, - CrawlRequestFromServer, - CrawlRequest, - CrawlRequestStats, - CrawlRequestStatsFromServer, - CrawlRule, - CrawlerRules, - CrawlEventFromServer, - CrawlEvent, - CrawlConfigFromServer, - CrawlConfig, - CrawlRequestWithDetailsFromServer, - CrawlRequestWithDetails, - DomainConfig, - DomainConfigFromServer, -} from './types'; - -export function crawlerDomainServerToClient(payload: CrawlerDomainFromServer): CrawlerDomain { - const { - id, - name, - sitemaps, - created_on: createdOn, - last_visited_at: lastCrawl, - document_count: documentCount, - crawl_rules: crawlRules, - default_crawl_rule: defaultCrawlRule, - entry_points: entryPoints, - deduplication_enabled: deduplicationEnabled, - deduplication_fields: deduplicationFields, - available_deduplication_fields: availableDeduplicationFields, - } = payload; - - const clientPayload: CrawlerDomain = { - id, - url: name, - documentCount, - createdOn, - crawlRules, - sitemaps, - entryPoints, - deduplicationEnabled, - deduplicationFields, - availableDeduplicationFields, - }; - - if (lastCrawl) { - clientPayload.lastCrawl = lastCrawl; - } - - if (defaultCrawlRule) { - clientPayload.defaultCrawlRule = defaultCrawlRule; - } - - return clientPayload; -} - -export function crawlRequestStatsServerToClient( - crawlStats: CrawlRequestStatsFromServer -): CrawlRequestStats { - const { - status: { - avg_response_time_msec: avgResponseTimeMSec, - crawl_duration_msec: crawlDurationMSec, - pages_visited: pagesVisited, - urls_allowed: urlsAllowed, - status_codes: statusCodes, - }, - } = crawlStats; - - return { - status: { - urlsAllowed, - pagesVisited, - avgResponseTimeMSec, - crawlDurationMSec, - statusCodes, - }, - }; -} - -export function crawlRequestServerToClient(crawlRequest: CrawlRequestFromServer): CrawlRequest { - const { - id, - status, - created_at: createdAt, - began_at: beganAt, - completed_at: completedAt, - } = crawlRequest; - - return { - id, - status, - createdAt, - beganAt, - completedAt, - }; -} - -export function crawlConfigServerToClient(crawlConfig: CrawlConfigFromServer): CrawlConfig { - const { - domain_allowlist: domainAllowlist, - seed_urls: seedUrls, - sitemap_urls: sitemapUrls, - max_crawl_depth: maxCrawlDepth, - } = crawlConfig; - - return { - domainAllowlist, - seedUrls, - sitemapUrls, - maxCrawlDepth, - }; -} - -export function crawlEventServerToClient(event: CrawlEventFromServer): CrawlEvent { - const { - id, - stage, - status, - created_at: createdAt, - began_at: beganAt, - completed_at: completedAt, - type, - crawl_config: crawlConfig, - } = event; - - return { - id, - stage, - status, - createdAt, - beganAt, - completedAt, - type, - crawlConfig: crawlConfigServerToClient(crawlConfig), - }; -} - -export function crawlRequestWithDetailsServerToClient( - event: CrawlRequestWithDetailsFromServer -): CrawlRequestWithDetails { - const { - began_at: beganAt, - completed_at: completedAt, - crawl_config: crawlConfig, - created_at: createdAt, - id, - stats: crawlStats, - status, - type, - } = event; - - return { - beganAt, - completedAt, - crawlConfig: crawlConfigServerToClient(crawlConfig), - createdAt, - id, - stats: crawlStats && crawlRequestStatsServerToClient(crawlStats), - status, - type, - }; -} - -export function crawlerDataServerToClient(payload: CrawlerDataFromServer): CrawlerData { - const { domains, events, most_recent_crawl_request: mostRecentCrawlRequest } = payload; - - return { - domains: domains.map((domain) => crawlerDomainServerToClient(domain)), - events: events.map((event) => crawlEventServerToClient(event)), - mostRecentCrawlRequest: - mostRecentCrawlRequest && crawlRequestServerToClient(mostRecentCrawlRequest), - }; -} - -export function crawlDomainValidationToResult( - data: CrawlerDomainValidationResultFromServer -): CrawlerDomainValidationStep { - if (!data.valid) { - return { - state: 'invalid', - blockingFailure: true, - message: data.results.find((result) => result.result === 'failure')?.comment, - }; - } - - const warningResult = data.results.find((result) => result.result === 'warning'); - - if (warningResult) { - return { - state: 'warning', - blockingFailure: !data.valid, - message: warningResult.comment, - }; - } - - return { - state: 'valid', - }; -} - -export const getDeleteDomainConfirmationMessage = (domainUrl: string) => { - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.confirmationPopupMessage', - { - defaultMessage: - 'Are you sure you want to remove the domain "{domainUrl}" and all of its settings?', - values: { - domainUrl, - }, - } - ); -}; - -export const getDeleteDomainSuccessMessage = (domainUrl: string) => { - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.successMessage', - { - defaultMessage: "Domain ''{domainUrl}'' was deleted", - values: { - domainUrl, - }, - } - ); -}; - -export const getCrawlRulePathPatternTooltip = (crawlRule: CrawlRule) => { - if (crawlRule.rule === CrawlerRules.regex) { - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.regexPathPatternTooltip', - { - defaultMessage: - 'The path pattern is a regular expression compatible with the Ruby language regular expression engine.', - } - ); - } - - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.pathPatternTooltip', - { - defaultMessage: - 'The path pattern is a literal string except for the asterisk (*) character, which is a meta character that will match anything.', - } - ); -}; - -export const domainConfigServerToClient = ( - domainConfigFromServer: DomainConfigFromServer -): DomainConfig => ({ - id: domainConfigFromServer.id, - name: domainConfigFromServer.name, - seedUrls: domainConfigFromServer.seed_urls, - sitemapUrls: domainConfigFromServer.sitemap_urls, -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts deleted file mode 100644 index 7468597294026..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const CREDENTIALS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.title', - { defaultMessage: 'Credentials' } -); - -export enum ApiTokenTypes { - Admin = 'admin', - Private = 'private', - Search = 'search', -} - -export const CREATE_MESSAGE = (name: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.tokens.created', { - defaultMessage: "API key ''{name}'' was created", - values: { name }, - }); -export const UPDATE_MESSAGE = (name: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.tokens.update', { - defaultMessage: "API key ''{name}'' was updated", - values: { name }, - }); -export const DELETE_MESSAGE = (name: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.tokens.deleted', { - defaultMessage: "API key ''{name}'' was deleted", - values: { name }, - }); - -export const SEARCH_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.permissions.display.search', - { - defaultMessage: 'search', - } -); -export const ALL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.permissions.display.all', - { - defaultMessage: 'all', - } -); -export const READ_WRITE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.permissions.display.readwrite', - { - defaultMessage: 'read/write', - } -); -export const READ_ONLY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.permissions.display.readonly', - { - defaultMessage: 'read-only', - } -); -export const WRITE_ONLY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.permissions.display.writeonly', - { - defaultMessage: 'write-only', - } -); - -export const TOKEN_TYPE_DESCRIPTION: { [key: string]: string } = { - [ApiTokenTypes.Search]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.search.description', - { - defaultMessage: 'Public Search Keys are used for search endpoints only.', - } - ), - [ApiTokenTypes.Private]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.private.description', - { - defaultMessage: - 'Private API Keys are used for read and/or write access on one or more Engines.', - } - ), - [ApiTokenTypes.Admin]: i18n.translate( - 'xpack.enterpriseSearch.appSearch.tokens.admin.description', - { - defaultMessage: 'Private Admin Keys are used to interact with the Credentials API.', - } - ), -}; - -export const TOKEN_TYPE_DISPLAY_NAMES: { [key: string]: string } = { - [ApiTokenTypes.Search]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.search.name', { - defaultMessage: 'Public Search Key', - }), - [ApiTokenTypes.Private]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.private.name', { - defaultMessage: 'Private API Key', - }), - [ApiTokenTypes.Admin]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.admin.name', { - defaultMessage: 'Private Admin Key', - }), -}; - -export const TOKEN_TYPE_INFO = [ - { value: ApiTokenTypes.Search, text: TOKEN_TYPE_DISPLAY_NAMES[ApiTokenTypes.Search] }, - { value: ApiTokenTypes.Private, text: TOKEN_TYPE_DISPLAY_NAMES[ApiTokenTypes.Private] }, - { value: ApiTokenTypes.Admin, text: TOKEN_TYPE_DISPLAY_NAMES[ApiTokenTypes.Admin] }, -]; - -export const FLYOUT_ARIA_LABEL_ID = 'credentialsFlyoutTitle'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx deleted file mode 100644 index 79b5e2add27c3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiSkeletonText, EuiCopy } from '@elastic/eui'; - -import { DEFAULT_META } from '../../../shared/constants'; -import { externalUrl } from '../../../shared/enterprise_search_url'; - -import { Credentials } from './credentials'; - -import { CredentialsFlyout } from './credentials_flyout'; -import { CredentialsList } from './credentials_list'; - -describe('Credentials', () => { - // Kea mocks - const values = { - meta: DEFAULT_META, - dataLoading: false, - }; - const actions = { - fetchCredentials: jest.fn(), - fetchDetails: jest.fn(), - resetCredentials: jest.fn(), - showCredentialsForm: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(CredentialsList)).toHaveLength(1); - }); - - it('fetches data on mount', () => { - shallow(); - expect(actions.fetchCredentials).toHaveBeenCalledTimes(1); - expect(actions.fetchDetails).toHaveBeenCalledTimes(1); - }); - - it('calls resetCredentials on unmount', () => { - shallow(); - unmountHandler(); - expect(actions.resetCredentials).toHaveBeenCalledTimes(1); - }); - - it('renders a limited UI if data is still loading', () => { - setMockValues({ ...values, dataLoading: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="CreateAPIKeyButton"]')).toHaveLength(0); - expect(wrapper.find(EuiSkeletonText).prop('isLoading')).toEqual(true); - }); - - it('renders the API endpoint and a button to copy it', () => { - externalUrl.enterpriseSearchUrl = 'http://localhost:3002'; - const copyMock = jest.fn(); - const wrapper = shallow(); - // We wrap children in a div so that `shallow` can render it. - const copyEl = shallow(
{wrapper.find(EuiCopy).props().children(copyMock)}
); - expect(copyEl.find('EuiButtonIcon').props().onClick).toEqual(copyMock); - expect(copyEl.text().replace('', '')).toEqual('http://localhost:3002'); - }); - - it('will show the Crendentials Flyout when the Create API Key button is pressed', () => { - const wrapper = shallow(); - const button: any = wrapper.find('[data-test-subj="CreateAPIKeyButton"]'); - button.props().onClick(); - expect(actions.showCredentialsForm).toHaveBeenCalledTimes(1); - }); - - it('will render CredentialsFlyout if shouldShowCredentialsForm is true', () => { - setMockValues({ ...values, shouldShowCredentialsForm: true }); - const wrapper = shallow(); - expect(wrapper.find(CredentialsFlyout)).toHaveLength(1); - }); - - it('will NOT render CredentialsFlyout if shouldShowCredentialsForm is false', () => { - setMockValues({ ...values, shouldShowCredentialsForm: false }); - const wrapper = shallow(); - expect(wrapper.find(CredentialsFlyout)).toHaveLength(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx deleted file mode 100644 index 9b85406225b76..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiTitle, - EuiPanel, - EuiCopy, - EuiButtonIcon, - EuiSpacer, - EuiButton, - EuiPageHeader, - EuiSkeletonText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { externalUrl } from '../../../shared/enterprise_search_url/external_url'; -import { AppSearchPageTemplate } from '../layout'; - -import { CREDENTIALS_TITLE } from './constants'; -import { CredentialsFlyout } from './credentials_flyout'; -import { CredentialsList } from './credentials_list'; -import { CredentialsLogic } from './credentials_logic'; - -export const Credentials: React.FC = () => { - const { fetchCredentials, fetchDetails, resetCredentials, showCredentialsForm } = - useActions(CredentialsLogic); - - const { meta, dataLoading, shouldShowCredentialsForm } = useValues(CredentialsLogic); - - useEffect(() => { - fetchCredentials(); - }, [meta.page.current]); - - useEffect(() => { - fetchDetails(); - return () => { - resetCredentials(); - }; - }, []); - - return ( - - {shouldShowCredentialsForm && } - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.apiEndpoint', { - defaultMessage: 'Endpoint', - })} -

-
- - {(copy) => ( - <> - - {externalUrl.enterpriseSearchUrl} - - )} - -
- - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.apiKeys', { - defaultMessage: 'API keys', - })} -

-
- {!dataLoading && ( - showCredentialsForm()} - > - {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.createKey', { - defaultMessage: 'Create key', - })} - - )} -
- - - - - - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx deleted file mode 100644 index 1ca6c0ce82855..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyoutBody, EuiForm } from '@elastic/eui'; - -import { ApiTokenTypes } from '../constants'; -import { defaultApiToken } from '../credentials_logic'; - -import { CredentialsFlyoutBody } from './body'; -import { - FormKeyName, - FormKeyType, - FormKeyReadWriteAccess, - FormKeyEngineAccess, - FormKeyUpdateWarning, -} from './form_components'; - -describe('CredentialsFlyoutBody', () => { - const values = { - activeApiToken: defaultApiToken, - activeApiTokenExists: false, - }; - const actions = { - onApiTokenChange: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFlyoutBody)).toHaveLength(1); - expect(wrapper.find(EuiForm)).toHaveLength(1); - }); - - it('shows the expected form components on default private key creation', () => { - const wrapper = shallow(); - - expect(wrapper.find(FormKeyName)).toHaveLength(1); - expect(wrapper.find(FormKeyType)).toHaveLength(1); - expect(wrapper.find(FormKeyReadWriteAccess)).toHaveLength(1); - expect(wrapper.find(FormKeyEngineAccess)).toHaveLength(1); - expect(wrapper.find(FormKeyUpdateWarning)).toHaveLength(0); - }); - - it('does not show read-write access options for search keys', () => { - setMockValues({ - ...values, - activeApiToken: { - ...defaultApiToken, - type: ApiTokenTypes.Search, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(FormKeyReadWriteAccess)).toHaveLength(0); - expect(wrapper.find(FormKeyEngineAccess)).toHaveLength(1); - }); - - it('does not show read-write or engine access options for admin keys', () => { - setMockValues({ - ...values, - activeApiToken: { - ...defaultApiToken, - type: ApiTokenTypes.Admin, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(FormKeyReadWriteAccess)).toHaveLength(0); - expect(wrapper.find(FormKeyEngineAccess)).toHaveLength(0); - }); - - it('shows a warning if updating an existing key', () => { - setMockValues({ ...values, activeApiTokenExists: true }); - const wrapper = shallow(); - - expect(wrapper.find(FormKeyUpdateWarning)).toHaveLength(1); - }); - - it('calls onApiTokenChange on form submit', () => { - const wrapper = shallow(); - - const preventDefault = jest.fn(); - wrapper.find(EuiForm).simulate('submit', { preventDefault }); - - expect(preventDefault).toHaveBeenCalled(); - expect(actions.onApiTokenChange).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx deleted file mode 100644 index def165f3f82a2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiFlyoutBody, EuiForm } from '@elastic/eui'; - -import { FlashMessages } from '../../../../shared/flash_messages'; -import { ApiTokenTypes } from '../constants'; -import { CredentialsLogic } from '../credentials_logic'; - -import { - FormKeyName, - FormKeyType, - FormKeyReadWriteAccess, - FormKeyEngineAccess, - FormKeyUpdateWarning, -} from './form_components'; - -export const CredentialsFlyoutBody: React.FC = () => { - const { onApiTokenChange } = useActions(CredentialsLogic); - const { activeApiToken, activeApiTokenExists } = useValues(CredentialsLogic); - - return ( - - - { - e.preventDefault(); - onApiTokenChange(); - }} - component="form" - > - - - {activeApiToken.type === ApiTokenTypes.Private && } - {activeApiToken.type !== ApiTokenTypes.Admin && } - - {activeApiTokenExists && } - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx deleted file mode 100644 index d3312b5a1e369..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyoutFooter, EuiButtonEmpty } from '@elastic/eui'; - -import { CredentialsFlyoutFooter } from './footer'; - -describe('CredentialsFlyoutFooter', () => { - const values = { - activeApiTokenExists: false, - }; - const actions = { - hideCredentialsForm: jest.fn(), - onApiTokenChange: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFlyoutFooter)).toHaveLength(1); - }); - - it('closes the flyout', () => { - const wrapper = shallow(); - const button = wrapper.find(EuiButtonEmpty); - button.simulate('click'); - expect(button.prop('children')).toEqual('Close'); - expect(actions.hideCredentialsForm).toHaveBeenCalled(); - }); - - it('renders action button text for new tokens', () => { - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="APIKeyActionButton"]'); - - expect(button.prop('children')).toEqual('Save'); - }); - - it('renders action button text for existing tokens', () => { - setMockValues({ activeApiTokenExists: true }); - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="APIKeyActionButton"]'); - - expect(button.prop('children')).toEqual('Update'); - }); - - it('calls onApiTokenChange on action button press', () => { - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="APIKeyActionButton"]'); - button.simulate('click'); - - expect(actions.onApiTokenChange).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx deleted file mode 100644 index 98facae7a6fb6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiButton, -} from '@elastic/eui'; - -import { - CLOSE_BUTTON_LABEL, - SAVE_BUTTON_LABEL, - UPDATE_BUTTON_LABEL, -} from '../../../../shared/constants'; - -import { CredentialsLogic } from '../credentials_logic'; - -export const CredentialsFlyoutFooter: React.FC = () => { - const { hideCredentialsForm, onApiTokenChange } = useActions(CredentialsLogic); - const { activeApiTokenExists } = useValues(CredentialsLogic); - - return ( - - - - - {CLOSE_BUTTON_LABEL} - - - - - {activeApiTokenExists ? UPDATE_BUTTON_LABEL : SAVE_BUTTON_LABEL} - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/index.ts deleted file mode 100644 index fa709778ee706..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { FormKeyName } from './key_name'; -export { FormKeyType } from './key_type'; -export { FormKeyReadWriteAccess } from './key_read_write_access'; -export { FormKeyEngineAccess } from './key_engine_access'; -export { FormKeyUpdateWarning } from './key_update_warning'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx deleted file mode 100644 index d39ffb2bea206..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiRadio, EuiCheckbox } from '@elastic/eui'; - -import { rerender } from '../../../../../test_helpers'; - -import { FormKeyEngineAccess, EngineSelection } from './key_engine_access'; - -describe('FormKeyEngineAccess', () => { - const values = { - myRole: { canAccessAllEngines: true }, - fullEngineAccessChecked: true, - }; - const actions = { - setAccessAllEngines: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiRadio)).toHaveLength(2); - expect(wrapper.find(EngineSelection)).toHaveLength(0); - }); - - it('hides the full access radio option if the user does not have access to all engines', () => { - setMockValues({ - ...values, - myRole: { canAccessAllEngines: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find('#all_engines').prop('hidden')).toEqual(true); - }); - - it('controls the checked values for access radios', () => { - setMockValues({ - ...values, - fullEngineAccessChecked: true, - }); - const wrapper = shallow(); - - expect(wrapper.find('#all_engines').prop('checked')).toEqual(true); - expect(wrapper.find('#all_engines').prop('value')).toEqual('true'); - expect(wrapper.find('#specific_engines').prop('checked')).toEqual(false); - expect(wrapper.find('#specific_engines').prop('value')).toEqual('false'); - - setMockValues({ - ...values, - fullEngineAccessChecked: false, - }); - rerender(wrapper); - - expect(wrapper.find('#all_engines').prop('checked')).toEqual(false); - expect(wrapper.find('#all_engines').prop('value')).toEqual('false'); - expect(wrapper.find('#specific_engines').prop('checked')).toEqual(true); - expect(wrapper.find('#specific_engines').prop('value')).toEqual('true'); - }); - - it('calls setAccessAllEngines when the radios are changed', () => { - const wrapper = shallow(); - - wrapper.find('#all_engines').simulate('change'); - expect(actions.setAccessAllEngines).toHaveBeenCalledWith(true); - - wrapper.find('#specific_engines').simulate('change'); - expect(actions.setAccessAllEngines).toHaveBeenCalledWith(false); - }); - - it('displays the engine selection panel if the limited access radio is selected', () => { - setMockValues({ - ...values, - fullEngineAccessChecked: false, - }); - const wrapper = shallow(); - - expect(wrapper.find(EngineSelection)).toHaveLength(1); - }); -}); - -describe('EngineSelection', () => { - const values = { - activeApiToken: { engines: [] }, - engines: [{ name: 'engine1' }, { name: 'engine2' }, { name: 'engine3' }], - }; - const actions = { - onEngineSelect: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find('h4').text()).toEqual('Select Engines'); - expect(wrapper.find(EuiCheckbox)).toHaveLength(3); - expect(wrapper.find(EuiCheckbox).first().prop('label')).toEqual('engine1'); - }); - - it('controls the engines checked state', () => { - setMockValues({ - ...values, - activeApiToken: { engines: ['engine3'] }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCheckbox).first().prop('checked')).toEqual(false); - expect(wrapper.find(EuiCheckbox).last().prop('checked')).toEqual(true); - }); - - it('calls onEngineSelect when the checkboxes are changed', () => { - const wrapper = shallow(); - - wrapper.find(EuiCheckbox).first().simulate('change'); - expect(actions.onEngineSelect).toHaveBeenCalledWith('engine1'); - - wrapper.find(EuiCheckbox).last().simulate('change'); - expect(actions.onEngineSelect).toHaveBeenCalledWith('engine3'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx deleted file mode 100644 index 7e40eb63338bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFormRow, - EuiRadio, - EuiCheckbox, - EuiText, - EuiTitle, - EuiSpacer, - EuiPanel, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AppLogic } from '../../../../app_logic'; -import { CredentialsLogic } from '../../credentials_logic'; - -export const FormKeyEngineAccess: React.FC = () => { - const { myRole } = useValues(AppLogic); - const { setAccessAllEngines } = useActions(CredentialsLogic); - const { fullEngineAccessChecked } = useValues(CredentialsLogic); - - return ( - <> - - - <> - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.label', - { defaultMessage: 'Full Engine Access' } - )} -

-
- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.fullAccess.helpText', - { defaultMessage: 'Access to all current and future Engines.' } - )} - - - } - hidden={!myRole.canAccessAllEngines} - checked={fullEngineAccessChecked} - value={fullEngineAccessChecked.toString()} - onChange={() => setAccessAllEngines(true)} - /> - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.label', - { defaultMessage: 'Limited Engine Access' } - )} -

-
- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.limitedAccess.helpText', - { defaultMessage: 'Limit key access to specific Engines.' } - )} - - - } - checked={!fullEngineAccessChecked} - value={(!fullEngineAccessChecked).toString()} - onChange={() => setAccessAllEngines(false)} - /> - -
- {!fullEngineAccessChecked && } - - ); -}; - -export const EngineSelection: React.FC = () => { - const { onEngineSelect } = useActions(CredentialsLogic); - const { activeApiToken, engines } = useValues(CredentialsLogic); - - return ( - <> - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.label', - { defaultMessage: 'Select Engines' } - )} -

-
- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formEngineAccess.engineAccess.helpText', - { defaultMessage: 'Engines which the key can access:' } - )} - - - {engines.map((engine) => ( - onEngineSelect(engine.name)} - /> - ))} -
- - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx deleted file mode 100644 index 240295394a2d3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; - -import { FormKeyName } from '.'; - -describe('FormKeyName', () => { - const values = { - activeApiToken: { name: '' }, - activeApiTokenRawName: '', - activeApiTokenExists: false, - }; - const actions = { - setNameInputBlurred: jest.fn(), - setTokenName: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldText)).toHaveLength(1); - expect(wrapper.find(EuiFieldText).prop('placeholder')).toEqual('i.e., my-engine-key'); - expect(wrapper.find(EuiFieldText).prop('value')).toEqual(''); - expect(wrapper.find(EuiFieldText).prop('disabled')).toEqual(false); - expect(wrapper.find(EuiFormRow).prop('helpText')).toEqual(''); - }); - - it('shows help text if the raw name does not match the expected name', () => { - setMockValues({ - ...values, - activeApiToken: { name: 'my-engine-key' }, - activeApiTokenRawName: 'my engine key!!', - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiFormRow).prop('helpText')).toEqual( - 'Your key will be named: my-engine-key' - ); - }); - - it('controls the input value', () => { - setMockValues({ - ...values, - activeApiTokenRawName: 'test', - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldText).prop('value')).toEqual('test'); - }); - - it('disables the input if editing an existing key', () => { - setMockValues({ - ...values, - activeApiTokenExists: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldText).prop('disabled')).toEqual(true); - }); - - it('calls setTokenName when the input value is changed', () => { - const wrapper = shallow(); - wrapper.find(EuiFieldText).simulate('change', { target: { value: 'changed' } }); - - expect(actions.setTokenName).toHaveBeenCalledWith('changed'); - }); - - it('calls setNameInputBlurred when the user stops focusing the input', () => { - const wrapper = shallow(); - wrapper.find(EuiFieldText).simulate('blur'); - - expect(actions.setNameInputBlurred).toHaveBeenCalledWith(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx deleted file mode 100644 index f4f4f5f0aaaaa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiFormRow, EuiFieldText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CredentialsLogic } from '../../credentials_logic'; - -export const FormKeyName: React.FC = () => { - const { setNameInputBlurred, setTokenName } = useActions(CredentialsLogic); - const { - activeApiToken: { name }, - activeApiTokenRawName: rawName, - activeApiTokenExists, - } = useValues(CredentialsLogic); - - return ( - - setTokenName(e.target.value)} - onBlur={() => setNameInputBlurred(true)} - autoComplete="off" - maxLength={64} - disabled={activeApiTokenExists} - required - fullWidth - autoFocus - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx deleted file mode 100644 index 0128509da1928..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCheckbox } from '@elastic/eui'; - -import { FormKeyReadWriteAccess } from '.'; - -describe('FormKeyReadWriteAccess', () => { - const values = { - activeApiToken: { read: false, write: false }, - }; - const actions = { - setTokenReadWrite: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find('h3').text()).toEqual('Read and Write Access Levels'); - expect(wrapper.find(EuiCheckbox)).toHaveLength(2); - }); - - it('controls the checked state for the read checkbox', () => { - setMockValues({ - ...values, - activeApiToken: { read: true, write: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find('#read').prop('checked')).toEqual(true); - expect(wrapper.find('#write').prop('checked')).toEqual(false); - }); - - it('controls the checked state for the write checkbox', () => { - setMockValues({ - ...values, - activeApiToken: { read: false, write: true }, - }); - const wrapper = shallow(); - - expect(wrapper.find('#read').prop('checked')).toEqual(false); - expect(wrapper.find('#write').prop('checked')).toEqual(true); - }); - - it('calls setTokenReadWrite when the checkboxes are changed', () => { - const wrapper = shallow(); - - wrapper.find('#read').simulate('change', { target: { name: 'read', checked: true } }); - expect(actions.setTokenReadWrite).toHaveBeenCalledWith({ name: 'read', checked: true }); - - wrapper.find('#write').simulate('change', { target: { name: 'write', checked: false } }); - expect(actions.setTokenReadWrite).toHaveBeenCalledWith({ name: 'write', checked: false }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx deleted file mode 100644 index f363f6978db29..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiCheckbox, EuiText, EuiTitle, EuiSpacer, EuiPanel } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CredentialsLogic } from '../../credentials_logic'; -import { TokenReadWrite } from '../../types'; - -export const FormKeyReadWriteAccess: React.FC = () => { - const { setTokenReadWrite } = useActions(CredentialsLogic); - const { activeApiToken } = useValues(CredentialsLogic); - - return ( - <> - - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.formReadWrite.label', { - defaultMessage: 'Read and Write Access Levels', - })} -

-
- - {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.formReadWrite.helpText', { - defaultMessage: 'Only applies to Private API Keys.', - })} - - - setTokenReadWrite(e.target as TokenReadWrite)} - label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formReadWrite.readLabel', - { defaultMessage: 'Read Access' } - )} - /> - setTokenReadWrite(e.target as TokenReadWrite)} - label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.credentials.formReadWrite.writeLabel', - { defaultMessage: 'Write Access' } - )} - /> -
- - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx deleted file mode 100644 index 6534d060a27c1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiSelect } from '@elastic/eui'; - -import { ApiTokenTypes, TOKEN_TYPE_INFO } from '../../constants'; - -import { FormKeyType } from '.'; - -describe('FormKeyType', () => { - const values = { - myRole: { credentialTypes: ['search', 'private', 'admin'] }, - activeApiToken: { type: ApiTokenTypes.Private }, - activeApiTokenExists: false, - }; - const actions = { - setTokenType: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiSelect)).toHaveLength(1); - expect(wrapper.find(EuiSelect).prop('options')).toEqual(TOKEN_TYPE_INFO); - expect(wrapper.find(EuiSelect).prop('value')).toEqual(ApiTokenTypes.Private); - expect(wrapper.find(EuiSelect).prop('disabled')).toEqual(false); - }); - - it('only shows the type options that the user has access to', () => { - setMockValues({ - ...values, - myRole: { credentialTypes: ['search'] }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSelect).prop('options')).toEqual([ - expect.objectContaining({ value: ApiTokenTypes.Search }), - ]); - }); - - it('controls the select value', () => { - setMockValues({ - ...values, - activeApiToken: { type: ApiTokenTypes.Search }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSelect).prop('value')).toEqual(ApiTokenTypes.Search); - }); - - it('disables the select if editing an existing key', () => { - setMockValues({ - ...values, - activeApiTokenExists: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSelect).prop('disabled')).toEqual(true); - }); - - it('calls setTokenType when the select value is changed', () => { - const wrapper = shallow(); - wrapper.find(EuiSelect).simulate('change', { target: { value: ApiTokenTypes.Admin } }); - - expect(actions.setTokenType).toHaveBeenCalledWith(ApiTokenTypes.Admin); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx deleted file mode 100644 index 5213f786dfead..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiFormRow, EuiSelect, EuiText, EuiLink } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../../shared/doc_links'; - -import { AppLogic } from '../../../../app_logic'; -import { TOKEN_TYPE_DESCRIPTION, TOKEN_TYPE_INFO } from '../../constants'; -import { CredentialsLogic } from '../../credentials_logic'; - -export const FormKeyType: React.FC = () => { - const { myRole } = useValues(AppLogic); - const { setTokenType } = useActions(CredentialsLogic); - const { activeApiToken, activeApiTokenExists } = useValues(CredentialsLogic); - - const tokenDescription = TOKEN_TYPE_DESCRIPTION[activeApiToken.type]; - const tokenOptions = TOKEN_TYPE_INFO.filter((typeInfo) => - myRole?.credentialTypes?.includes(typeInfo.value) - ); - - return ( - -

- {tokenDescription}{' '} - - {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.documentationLink1', { - defaultMessage: 'Visit the documentation', - })} - {' '} - {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.documentationLink2', { - defaultMessage: 'to learn more about keys.', - })} -

- - } - > - setTokenType(e.target.value)} - disabled={activeApiTokenExists} - required - fullWidth - /> -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx deleted file mode 100644 index de1e3ad2358de..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { EuiCallOut } from '@elastic/eui'; - -import { FormKeyUpdateWarning } from '.'; - -describe('FormKeyUpdateWarning', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx deleted file mode 100644 index b3ff6b952053a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { EuiSpacer, EuiCallOut } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const FormKeyUpdateWarning: React.FC = () => ( - <> - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.updateWarning', { - defaultMessage: - 'Existing API keys may be shared between users. Changing permissions for this key will affect all users who have access to this key.', - })} -

-
- -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx deleted file mode 100644 index 019a6c7043fb0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyoutHeader } from '@elastic/eui'; - -import { ApiTokenTypes } from '../constants'; -import { ApiToken } from '../types'; - -import { CredentialsFlyoutHeader } from './header'; - -describe('CredentialsFlyoutHeader', () => { - const apiToken: ApiToken = { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - }; - const values = { - activeApiToken: apiToken, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFlyoutHeader)).toHaveLength(1); - expect(wrapper.find('h2').prop('id')).toEqual('credentialsFlyoutTitle'); - expect(wrapper.find('h2').prop('children')).toEqual('Create a new key'); - }); - - it('changes the title text if editing an existing token', () => { - setMockValues({ - activeApiToken: { - ...apiToken, - id: 'some-id', - name: 'search-key', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find('h2').prop('children')).toEqual('Update search-key'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx deleted file mode 100644 index 586ddc5c22b97..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { FLYOUT_ARIA_LABEL_ID } from '../constants'; -import { CredentialsLogic } from '../credentials_logic'; - -export const CredentialsFlyoutHeader: React.FC = () => { - const { activeApiToken } = useValues(CredentialsLogic); - - return ( - - -

- {activeApiToken.id - ? i18n.translate('xpack.enterpriseSearch.appSearch.credentials.flyout.updateTitle', { - defaultMessage: 'Update {tokenName}', - values: { tokenName: activeApiToken.name }, - }) - : i18n.translate('xpack.enterpriseSearch.appSearch.credentials.flyout.createTitle', { - defaultMessage: 'Create a new key', - })} -

-
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx deleted file mode 100644 index 904a3b76d1486..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyout } from '@elastic/eui'; - -import { CredentialsFlyout } from '.'; - -describe('CredentialsFlyout', () => { - const actions = { - hideCredentialsForm: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - const flyout = wrapper.find(EuiFlyout); - - expect(flyout).toHaveLength(1); - expect(flyout.prop('aria-labelledby')).toEqual('credentialsFlyoutTitle'); - expect(flyout.prop('onClose')).toEqual(actions.hideCredentialsForm); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx deleted file mode 100644 index 2ee73a6b80b5a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiPortal, EuiFlyout } from '@elastic/eui'; - -import { FLYOUT_ARIA_LABEL_ID } from '../constants'; -import { CredentialsLogic } from '../credentials_logic'; - -import { CredentialsFlyoutBody } from './body'; -import { CredentialsFlyoutFooter } from './footer'; -import { CredentialsFlyoutHeader } from './header'; - -export const CredentialsFlyout: React.FC = () => { - const { hideCredentialsForm } = useActions(CredentialsLogic); - - return ( - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx deleted file mode 100644 index ebfd1d7eadb96..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx +++ /dev/null @@ -1,285 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBasicTable, EuiCopy } from '@elastic/eui'; - -import { HiddenText } from '../../../../shared/hidden_text'; -import { ApiTokenTypes } from '../constants'; -import { ApiToken } from '../types'; - -import { Key } from './key'; - -import { CredentialsList } from '.'; - -describe('CredentialsList', () => { - const apiToken: ApiToken = { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - key: 'abc-1234', - }; - - // Kea mocks - const values = { - apiTokens: [apiToken], - meta: { - page: { - current: 1, - size: 10, - total_pages: 1, - total_results: 1, - }, - }, - isCredentialsDataComplete: true, - }; - const actions = { - deleteApiKey: jest.fn(), - onPaginate: jest.fn(), - showCredentialsForm: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiBasicTable)).toHaveLength(1); - }); - - describe('items', () => { - it('sorts items by id', () => { - setMockValues({ - ...values, - apiTokens: [ - { - ...apiToken, - id: 2, - }, - { - ...apiToken, - id: undefined, - }, - { - ...apiToken, - id: 1, - }, - ], - }); - const wrapper = shallow(); - const items = wrapper.find(EuiBasicTable).props().items as ApiToken[]; - expect(items.map((i) => i.id)).toEqual([undefined, 1, 2]); - }); - }); - - describe('empty state', () => { - it('renders an EuiEmptyPrompt when no credentials are available', () => { - setMockValues({ - ...values, - apiTokens: [], - }); - - const wrapper = shallow(); - expect(wrapper.render().find('.euiEmptyPrompt')).toHaveLength(1); - }); - }); - - describe('loading state', () => { - it('renders as loading when isCredentialsDataComplete is false', () => { - setMockValues({ - ...values, - isCredentialsDataComplete: false, - }); - - const wrapper = shallow(); - expect(wrapper.find(EuiBasicTable).prop('loading')).toBe(true); - }); - }); - - describe('pagination', () => { - it('derives pagination from meta object', () => { - setMockValues({ - ...values, - meta: { - page: { - current: 6, - size: 55, - total_pages: 1, - total_results: 1004, - }, - }, - }); - const wrapper = shallow(); - const { pagination } = wrapper.find(EuiBasicTable).props(); - expect(pagination).toEqual({ - pageIndex: 5, - pageSize: 55, - totalItemCount: 1004, - showPerPageOptions: false, - }); - }); - }); - - describe('columns', () => { - let columns: any[]; - - beforeAll(() => { - setMockValues(values); - const wrapper = shallow(); - columns = wrapper.find(EuiBasicTable).props().columns; - }); - - describe('column 1 (name)', () => { - const token = { - ...apiToken, - name: 'some-name', - }; - - it('renders correctly', () => { - const column = columns[0]; - const wrapper = shallow(
{column.render(token)}
); - expect(wrapper.text()).toEqual('some-name'); - }); - }); - - describe('column 2 (type)', () => { - const token = { - ...apiToken, - type: ApiTokenTypes.Private, - }; - - it('renders correctly', () => { - const column = columns[1]; - const wrapper = shallow(
{column.render(token)}
); - expect(wrapper.text()).toEqual('Private API Key'); - }); - }); - - describe('column 3 (key)', () => { - const token = { - ...apiToken, - key: 'abc-123', - }; - - it('renders nothing if no key is present', () => { - const tokenWithNoKey = { - key: undefined, - }; - const column = columns[2]; - const wrapper = shallow(
{column.render(tokenWithNoKey)}
); - expect(wrapper.text()).toBe(''); - }); - - it('renders an EuiCopy component with the key', () => { - const column = columns[2]; - const wrapper = shallow(
{column.render(token)}
); - expect(wrapper.find(EuiCopy).props().textToCopy).toEqual('abc-123'); - }); - - it('renders a HiddenText component with the key', () => { - const column = columns[2]; - const wrapper = shallow(
{column.render(token)}
) - .find(EuiCopy) - .dive(); - expect(wrapper.find(HiddenText).props().text).toEqual('abc-123'); - }); - - it('renders a Key component', () => { - const column = columns[2]; - const wrapper = shallow(
{column.render(token)}
) - .find(EuiCopy) - .dive() - .find(HiddenText) - .dive(); - expect(wrapper.find(Key).props()).toEqual({ - copy: expect.any(Function), - toggleIsHidden: expect.any(Function), - isHidden: expect.any(Boolean), - text: ( - - ••••••• - - ), - }); - }); - }); - - describe('column 4 (modes)', () => { - const token = { - ...apiToken, - type: ApiTokenTypes.Admin, - }; - - it('renders correctly', () => { - const column = columns[3]; - const wrapper = shallow(
{column.render(token)}
); - expect(wrapper.text()).toEqual('--'); - }); - }); - - describe('column 5 (engines)', () => { - const token = { - ...apiToken, - type: ApiTokenTypes.Private, - access_all_engines: true, - }; - - it('renders correctly', () => { - const column = columns[4]; - const wrapper = shallow(
{column.render(token)}
); - expect(wrapper.text()).toEqual('all'); - }); - }); - - describe('column 6 (edit action)', () => { - const token = apiToken; - - it('calls showCredentialsForm when clicked', () => { - const action = columns[5].actions[0]; - action.onClick(token); - expect(actions.showCredentialsForm).toHaveBeenCalledWith(token); - }); - }); - - describe('column 7 (delete action)', () => { - const token = { - ...apiToken, - name: 'some-name', - }; - - it('calls deleteApiKey when clicked', () => { - const action = columns[5].actions[1]; - action.onClick(token); - expect(actions.deleteApiKey).toHaveBeenCalledWith('some-name'); - }); - }); - }); - - describe('onChange', () => { - it('will handle pagination by calling `onPaginate`', () => { - const wrapper = shallow(); - wrapper.find(EuiBasicTable).simulate('change', { - page: { - size: 10, - index: 2, - }, - }); - - expect(actions.onPaginate).toHaveBeenCalledWith(3); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx deleted file mode 100644 index 4f45da9e26046..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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, { useMemo } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiButton, - EuiCopy, - EuiEmptyPrompt, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EDIT_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants'; -import { docLinks } from '../../../../shared/doc_links'; -import { HiddenText } from '../../../../shared/hidden_text'; -import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; -import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants'; -import { CredentialsLogic } from '../credentials_logic'; -import { ApiToken } from '../types'; -import { getModeDisplayText, getEnginesDisplayText } from '../utils'; -import { apiTokenSort } from '../utils/api_token_sort'; - -import { Key } from './key'; - -export const CredentialsList: React.FC = () => { - const { deleteApiKey, onPaginate, showCredentialsForm } = useActions(CredentialsLogic); - - const { apiTokens, meta, isCredentialsDataComplete } = useValues(CredentialsLogic); - - const items = useMemo(() => apiTokens.slice().sort(apiTokenSort), [apiTokens]); - - const columns: Array> = [ - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.nameTitle', { - defaultMessage: 'Name', - }), - width: '12%', - render: (token: ApiToken) => token.name, - }, - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.typeTitle', { - defaultMessage: 'Type', - }), - width: '15%', - render: (token: ApiToken) => TOKEN_TYPE_DISPLAY_NAMES[token.type], - }, - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.keyTitle', { - defaultMessage: 'Key', - }), - width: '36%', - className: 'eui-textBreakAll', - render: (token: ApiToken) => { - const { key } = token; - if (!key) return null; - return ( - - {(copy) => ( - - {({ hiddenText, isHidden, toggle }) => ( - - )} - - )} - - ); - }, - mobileOptions: { - // @ts-ignore - EUI's type definitions need to be updated - width: '100%', - }, - }, - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.modesTitle', { - defaultMessage: 'Modes', - }), - width: '10%', - render: (token: ApiToken) => getModeDisplayText(token), - }, - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.list.enginesTitle', { - defaultMessage: 'Engines', - }), - width: '18%', - render: (token: ApiToken) => getEnginesDisplayText(token), - }, - { - actions: [ - { - name: EDIT_BUTTON_LABEL, - description: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.editKey', { - defaultMessage: 'Edit API Key', - }), - type: 'icon', - icon: 'pencil', - color: 'primary', - onClick: (token: ApiToken) => showCredentialsForm(token), - }, - { - name: DELETE_BUTTON_LABEL, - description: i18n.translate('xpack.enterpriseSearch.appSearch.credentials.deleteKey', { - defaultMessage: 'Delete API Key', - }), - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (token: ApiToken) => deleteApiKey(token.name), - }, - ], - }, - ]; - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.empty.title', { - defaultMessage: 'Create your first API key', - })} - - } - body={i18n.translate('xpack.enterpriseSearch.appSearch.credentials.empty.body', { - defaultMessage: 'Allow applications to access Elastic App Search on your behalf.', - })} - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.empty.buttonLabel', { - defaultMessage: 'Learn about API keys', - })} - - } - /> - } - loading={!isCredentialsDataComplete} - pagination={{ - ...convertMetaToPagination(meta), - showPerPageOptions: false, - }} - onChange={handlePageChange(onPaginate)} - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/index.ts deleted file mode 100644 index 7adf1c113e27d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { CredentialsList } from './credentials_list'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx deleted file mode 100644 index 5e042319ae613..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { EuiButtonIcon } from '@elastic/eui'; - -import { Key } from './key'; - -describe('Key', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - const props = { - copy: jest.fn(), - toggleIsHidden: jest.fn(), - isHidden: true, - text: 'some-api-key', - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButtonIcon).length).toEqual(2); - }); - - it('will call copy when the first button is clicked', () => { - const wrapper = shallow(); - wrapper.find(EuiButtonIcon).first().simulate('click'); - expect(props.copy).toHaveBeenCalled(); - }); - - it('will call hide when the second button is clicked', () => { - const wrapper = shallow(); - wrapper.find(EuiButtonIcon).last().simulate('click'); - expect(props.toggleIsHidden).toHaveBeenCalled(); - }); - - it('will render the "eye" icon when isHidden is true', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButtonIcon).last().prop('iconType')).toBe('eye'); - }); - - it('will render the "eyeClosed" icon when isHidden is false', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButtonIcon).last().prop('iconType')).toBe('eyeClosed'); - }); - - it('will render the provided text', () => { - const wrapper = shallow(); - expect(wrapper.text()).toContain('some-api-key'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx deleted file mode 100644 index ff14379b9aecc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { EuiButtonIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -interface Props { - copy: () => void; - toggleIsHidden: () => void; - isHidden: boolean; - text: React.ReactNode; -} - -export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => { - const hideIcon = isHidden ? 'eye' : 'eyeClosed'; - const hideIconLabel = isHidden - ? i18n.translate('xpack.enterpriseSearch.appSearch.credentials.showApiKey', { - defaultMessage: 'Show API Key', - }) - : i18n.translate('xpack.enterpriseSearch.appSearch.credentials.hideApiKey', { - defaultMessage: 'Hide API Key', - }); - - return ( - <> - - - {text} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts deleted file mode 100644 index 4ccd91959831a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts +++ /dev/null @@ -1,1309 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { DEFAULT_META } from '../../../shared/constants'; - -jest.mock('../../app_logic', () => ({ - AppLogic: { - selectors: { myRole: jest.fn(() => ({})) }, - }, -})); -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; -import { AppLogic } from '../../app_logic'; - -import { EngineTypes } from '../engine/types'; - -import { ApiTokenTypes } from './constants'; - -import { CredentialsLogic } from './credentials_logic'; - -describe('CredentialsLogic', () => { - const { mount } = new LogicMounter(CredentialsLogic); - const { http } = mockHttpValues; - const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - activeApiToken: { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - }, - activeApiTokenExists: false, - activeApiTokenRawName: '', - apiTokens: [], - dataLoading: true, - engines: [], - formErrors: [], - isCredentialsDataComplete: false, - isCredentialsDetailsComplete: false, - meta: DEFAULT_META, - nameInputBlurred: false, - shouldShowCredentialsForm: false, - fullEngineAccessChecked: false, - }; - - const newToken = { - id: 1, - name: 'myToken', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - engines: [], - }; - - const credentialsDetails = { - engines: [ - { name: 'engine1', type: EngineTypes.indexed, language: 'english', result_fields: {} }, - { name: 'engine1', type: EngineTypes.indexed, language: 'english', result_fields: {} }, - ], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(CredentialsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('addEngineName', () => { - const values = { - ...DEFAULT_VALUES, - activeApiToken: expect.any(Object), - activeApiTokenExists: expect.any(Boolean), - }; - - describe('activeApiToken', () => { - it("should add an engine to the active api token's engine list", () => { - mount({ - activeApiToken: { - ...newToken, - engines: ['someEngine'], - }, - }); - - CredentialsLogic.actions.addEngineName('newEngine'); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { ...newToken, engines: ['someEngine', 'newEngine'] }, - }); - }); - - it("should create a new engines list if one doesn't exist", () => { - mount({ - activeApiToken: { - ...newToken, - engines: undefined, - }, - }); - - CredentialsLogic.actions.addEngineName('newEngine'); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { ...newToken, engines: ['newEngine'] }, - }); - }); - }); - }); - - describe('removeEngineName', () => { - describe('activeApiToken', () => { - const values = { - ...DEFAULT_VALUES, - activeApiToken: expect.any(Object), - activeApiTokenExists: expect.any(Boolean), - }; - - it("should remove an engine from the active api token's engine list", () => { - mount({ - activeApiToken: { - ...newToken, - engines: ['someEngine', 'anotherEngine'], - }, - }); - - CredentialsLogic.actions.removeEngineName('someEngine'); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { ...newToken, engines: ['anotherEngine'] }, - }); - }); - - it('will not remove the engine if it is not found', () => { - mount({ - activeApiToken: { - ...newToken, - engines: ['someEngine', 'anotherEngine'], - }, - }); - - CredentialsLogic.actions.removeEngineName('notfound'); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { ...newToken, engines: ['someEngine', 'anotherEngine'] }, - }); - }); - - it('does not throw a type error if no engines are stored in state', () => { - mount({ - activeApiToken: {}, - }); - CredentialsLogic.actions.removeEngineName(''); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { engines: [] }, - }); - }); - }); - }); - - describe('setAccessAllEngines', () => { - const values = { - ...DEFAULT_VALUES, - activeApiToken: expect.any(Object), - activeApiTokenExists: expect.any(Boolean), - }; - - describe('activeApiToken', () => { - it('should set the value of access_all_engines and clear out engines list if true', () => { - mount({ - activeApiToken: { - ...newToken, - access_all_engines: false, - engines: ['someEngine', 'anotherEngine'], - }, - }); - - CredentialsLogic.actions.setAccessAllEngines(true); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { ...newToken, engines: [], access_all_engines: true }, - }); - }); - - it('should set the value of access_all_engines and but maintain engines list if false', () => { - mount({ - activeApiToken: { - ...newToken, - access_all_engines: true, - engines: ['someEngine', 'anotherEngine'], - }, - }); - - CredentialsLogic.actions.setAccessAllEngines(false); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...newToken, - access_all_engines: false, - engines: ['someEngine', 'anotherEngine'], - }, - }); - }); - }); - }); - - describe('onApiTokenCreateSuccess', () => { - const values = { - ...DEFAULT_VALUES, - activeApiTokenExists: expect.any(Boolean), - apiTokens: expect.any(Array), - activeApiToken: expect.any(Object), - activeApiTokenRawName: expect.any(String), - shouldShowCredentialsForm: expect.any(Boolean), - formErrors: expect.any(Array), - }; - - describe('apiTokens', () => { - const existingToken = { - name: 'some_token', - type: ApiTokenTypes.Private, - }; - - it('should add the provided token to the apiTokens list', () => { - mount({ - apiTokens: [existingToken], - }); - - CredentialsLogic.actions.onApiTokenCreateSuccess(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - apiTokens: [existingToken, newToken], - }); - }); - }); - - describe('activeApiToken', () => { - it('should reset to the default value, which effectively clears out the current form', () => { - mount({ - activeApiToken: newToken, - }); - - CredentialsLogic.actions.onApiTokenCreateSuccess(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: DEFAULT_VALUES.activeApiToken, - }); - }); - }); - - describe('activeApiTokenRawName', () => { - it('should reset to the default value, which effectively clears out the current form', () => { - mount({ - activeApiTokenRawName: 'foo', - }); - - CredentialsLogic.actions.onApiTokenCreateSuccess(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiTokenRawName: DEFAULT_VALUES.activeApiTokenRawName, - }); - }); - }); - - describe('shouldShowCredentialsForm', () => { - it('should reset to the default value, which closes the credentials form', () => { - mount({ - shouldShowCredentialsForm: true, - }); - - CredentialsLogic.actions.onApiTokenCreateSuccess(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - shouldShowCredentialsForm: false, - }); - }); - }); - - describe('formErrors', () => { - it('should reset `formErrors`', () => { - mount({ - formErrors: ['I am an error'], - }); - - CredentialsLogic.actions.onApiTokenCreateSuccess(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - formErrors: [], - }); - }); - }); - }); - - describe('onApiTokenError', () => { - const values = { - ...DEFAULT_VALUES, - formErrors: expect.any(Array), - }; - - describe('formErrors', () => { - it('should set `formErrors`', () => { - mount({ - formErrors: ['I am an error'], - }); - - CredentialsLogic.actions.onApiTokenError(['I am the NEW error']); - expect(CredentialsLogic.values).toEqual({ - ...values, - formErrors: ['I am the NEW error'], - }); - }); - }); - }); - - describe('onApiTokenUpdateSuccess', () => { - const values = { - ...DEFAULT_VALUES, - apiTokens: expect.any(Array), - activeApiToken: expect.any(Object), - activeApiTokenRawName: expect.any(String), - shouldShowCredentialsForm: expect.any(Boolean), - }; - - describe('apiTokens', () => { - const existingToken = { - name: 'some_token', - type: ApiTokenTypes.Private, - }; - - it('should replace the existing token with the new token by name', () => { - mount({ - apiTokens: [newToken, existingToken], - }); - const updatedExistingToken = { - ...existingToken, - type: ApiTokenTypes.Admin, - }; - - CredentialsLogic.actions.onApiTokenUpdateSuccess(updatedExistingToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - apiTokens: [newToken, updatedExistingToken], - }); - }); - - // TODO Not sure if this is a good behavior or not - it('if for some reason the existing token is not found, it adds a new token...', () => { - mount({ - apiTokens: [newToken, existingToken], - }); - const brandNewToken = { - name: 'brand new token', - type: ApiTokenTypes.Admin, - }; - - CredentialsLogic.actions.onApiTokenUpdateSuccess(brandNewToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - apiTokens: [newToken, existingToken, brandNewToken], - }); - }); - }); - - describe('activeApiToken', () => { - it('should reset to the default value, which effectively clears out the current form', () => { - mount({ - activeApiToken: newToken, - }); - - CredentialsLogic.actions.onApiTokenUpdateSuccess({ - ...newToken, - type: ApiTokenTypes.Admin, - }); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: DEFAULT_VALUES.activeApiToken, - }); - }); - }); - - describe('activeApiTokenRawName', () => { - it('should reset to the default value, which effectively clears out the current form', () => { - mount({ - activeApiTokenRawName: 'foo', - }); - - CredentialsLogic.actions.onApiTokenUpdateSuccess({ - ...newToken, - type: ApiTokenTypes.Admin, - }); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiTokenRawName: DEFAULT_VALUES.activeApiTokenRawName, - }); - }); - }); - - describe('shouldShowCredentialsForm', () => { - it('should reset to the default value, which closes the credentials form', () => { - mount({ - shouldShowCredentialsForm: true, - }); - - CredentialsLogic.actions.onApiTokenUpdateSuccess({ - ...newToken, - type: ApiTokenTypes.Admin, - }); - expect(CredentialsLogic.values).toEqual({ - ...values, - shouldShowCredentialsForm: false, - }); - }); - }); - }); - - describe('setCredentialsData', () => { - const meta = { - page: { - current: 1, - size: 1, - total_pages: 1, - total_results: 1, - }, - }; - - const values = { - ...DEFAULT_VALUES, - dataLoading: false, - apiTokens: expect.any(Array), - meta: expect.any(Object), - isCredentialsDataComplete: expect.any(Boolean), - }; - - describe('apiTokens', () => { - it('should be set', () => { - mount(); - - CredentialsLogic.actions.setCredentialsData(meta, [newToken, newToken]); - expect(CredentialsLogic.values).toEqual({ - ...values, - apiTokens: [newToken, newToken], - }); - }); - }); - - describe('meta', () => { - it('should be set', () => { - mount(); - - CredentialsLogic.actions.setCredentialsData(meta, [newToken, newToken]); - expect(CredentialsLogic.values).toEqual({ - ...values, - meta, - }); - }); - }); - - describe('isCredentialsDataComplete', () => { - it('should be set to true so we know that data fetching has completed', () => { - mount({ - isCredentialsDataComplete: false, - }); - - CredentialsLogic.actions.setCredentialsData(meta, [newToken, newToken]); - expect(CredentialsLogic.values).toEqual({ - ...values, - isCredentialsDataComplete: true, - }); - }); - }); - }); - - describe('setCredentialsDetails', () => { - const values = { - ...DEFAULT_VALUES, - dataLoading: false, - engines: expect.any(Array), - isCredentialsDetailsComplete: expect.any(Boolean), - }; - - describe('isCredentialsDataComplete', () => { - it('should be set to true so that we know data fetching has been completed', () => { - mount({ - isCredentialsDetailsComplete: false, - }); - - CredentialsLogic.actions.setCredentialsDetails(credentialsDetails); - expect(CredentialsLogic.values).toEqual({ - ...values, - isCredentialsDetailsComplete: true, - }); - }); - }); - - describe('engines', () => { - it('should set `engines` from the provided details object', () => { - mount({ - engines: [], - }); - - CredentialsLogic.actions.setCredentialsDetails(credentialsDetails); - expect(CredentialsLogic.values).toEqual({ - ...values, - engines: credentialsDetails.engines, - }); - }); - }); - }); - - describe('setNameInputBlurred', () => { - const values = { - ...DEFAULT_VALUES, - nameInputBlurred: expect.any(Boolean), - }; - - describe('nameInputBlurred', () => { - it('should set this value', () => { - mount({ - nameInputBlurred: false, - }); - - CredentialsLogic.actions.setNameInputBlurred(true); - expect(CredentialsLogic.values).toEqual({ - ...values, - nameInputBlurred: true, - }); - }); - }); - }); - - describe('setTokenReadWrite', () => { - const values = { - ...DEFAULT_VALUES, - activeApiToken: expect.any(Object), - activeApiTokenExists: expect.any(Boolean), - }; - - describe('activeApiToken', () => { - it('should set "read" or "write" values', () => { - mount({ - activeApiToken: { - ...newToken, - read: false, - }, - }); - - CredentialsLogic.actions.setTokenReadWrite({ name: 'read', checked: true }); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...newToken, - read: true, - }, - }); - }); - }); - }); - - describe('setTokenName', () => { - const values = { - ...DEFAULT_VALUES, - activeApiToken: expect.any(Object), - activeApiTokenRawName: expect.any(String), - activeApiTokenExists: expect.any(Boolean), - }; - - describe('activeApiToken', () => { - it('update the name property on the activeApiToken, formatted correctly', () => { - mount({ - activeApiToken: { - ...newToken, - name: 'bar', - }, - }); - - CredentialsLogic.actions.setTokenName('New Name'); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { ...newToken, name: 'new-name' }, - }); - }); - }); - - describe('activeApiTokenRawName', () => { - it('updates the raw name, with no formatting applied', () => { - mount(); - - CredentialsLogic.actions.setTokenName('New Name'); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiTokenRawName: 'New Name', - }); - }); - }); - }); - - describe('setTokenType', () => { - const values = { - ...DEFAULT_VALUES, - activeApiToken: { - ...newToken, - type: expect.any(String), - read: expect.any(Boolean), - write: expect.any(Boolean), - access_all_engines: expect.any(Boolean), - engines: expect.any(Array), - }, - activeApiTokenExists: expect.any(Boolean), - }; - - describe('activeApiToken.access_all_engines', () => { - describe('when value is admin', () => { - it('updates access_all_engines to false', () => { - mount({ - activeApiToken: { - ...newToken, - access_all_engines: true, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Admin); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - access_all_engines: false, - }, - }); - }); - }); - - describe('when value is not admin', () => { - it('will maintain access_all_engines value when true', () => { - mount({ - activeApiToken: { - ...newToken, - access_all_engines: true, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Private); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - access_all_engines: true, - }, - }); - }); - - it('will maintain access_all_engines value when false', () => { - mount({ - activeApiToken: { - ...newToken, - access_all_engines: false, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Private); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - access_all_engines: false, - }, - }); - }); - }); - }); - - describe('activeApiToken.engines', () => { - describe('when value is admin', () => { - it('clears the array', () => { - mount({ - activeApiToken: { - ...newToken, - engines: [{}, {}], - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Admin); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - engines: [], - }, - }); - }); - }); - - describe('when value is not admin', () => { - it('will maintain engines array', () => { - mount({ - activeApiToken: { - ...newToken, - engines: [{}, {}], - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Private); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - engines: [{}, {}], - }, - }); - }); - }); - }); - - describe('activeApiToken.write', () => { - describe('when value is private', () => { - it('sets this to true', () => { - mount({ - activeApiToken: { - ...newToken, - write: false, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Private); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - write: true, - }, - }); - }); - }); - - describe('when value is not private', () => { - it('sets this to false', () => { - mount({ - activeApiToken: { - ...newToken, - write: true, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Admin); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - write: false, - }, - }); - }); - }); - }); - - describe('activeApiToken.read', () => { - describe('when value is private', () => { - it('sets this to true', () => { - mount({ - activeApiToken: { - ...newToken, - read: false, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Private); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - read: true, - }, - }); - }); - }); - - describe('when value is not private', () => { - it('sets this to false', () => { - mount({ - activeApiToken: { - ...newToken, - read: true, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Admin); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - read: false, - }, - }); - }); - }); - }); - - describe('activeApiToken.type', () => { - it('sets the type value', () => { - mount({ - activeApiToken: { - ...newToken, - type: ApiTokenTypes.Admin, - }, - }); - - CredentialsLogic.actions.setTokenType(ApiTokenTypes.Private); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: { - ...values.activeApiToken, - type: ApiTokenTypes.Private, - }, - }); - }); - }); - }); - - describe('showCredentialsForm', () => { - const values = { - ...DEFAULT_VALUES, - activeApiTokenExists: expect.any(Boolean), - activeApiToken: expect.any(Object), - activeApiTokenRawName: expect.any(String), - formErrors: expect.any(Array), - shouldShowCredentialsForm: expect.any(Boolean), - }; - - describe('shouldShowCredentialsForm', () => { - it('should toggle `shouldShowCredentialsForm`', () => { - mount({ - shouldShowCredentialsForm: false, - }); - - CredentialsLogic.actions.showCredentialsForm(); - expect(CredentialsLogic.values).toEqual({ - ...values, - shouldShowCredentialsForm: true, - }); - }); - }); - - describe('formErrors', () => { - it('should reset `formErrors`', () => { - mount({ - formErrors: ['I am an error'], - }); - - CredentialsLogic.actions.showCredentialsForm(); - expect(CredentialsLogic.values).toEqual({ - ...values, - formErrors: [], - }); - }); - }); - - describe('activeApiTokenRawName', () => { - it('should set `activeApiTokenRawName` to the name of the provided token', () => { - mount({ - activeApiTokenRawName: 'Some Name', - }); - - CredentialsLogic.actions.showCredentialsForm(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiTokenRawName: 'myToken', - }); - }); - - it('should set `activeApiTokenRawName` to the default value if no token is provided', () => { - mount({ - activeApiTokenRawName: 'Some Name', - }); - - CredentialsLogic.actions.showCredentialsForm(); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiTokenRawName: DEFAULT_VALUES.activeApiTokenRawName, - }); - }); - }); - - describe('activeApiToken', () => { - it('should set `activeApiToken` to the provided token', () => { - mount(); - - CredentialsLogic.actions.showCredentialsForm(newToken); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: newToken, - }); - }); - - it('should set `activeApiToken` to the default value if no token is provided', () => { - mount({ - activeApiToken: newToken, - }); - - CredentialsLogic.actions.showCredentialsForm(); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiToken: DEFAULT_VALUES.activeApiToken, - }); - }); - }); - - describe('listener side-effects', () => { - it('should clear flashMessages whenever the credentials form flyout is opened', () => { - CredentialsLogic.actions.showCredentialsForm(); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - }); - - describe('hideCredentialsForm', () => { - const values = { - ...DEFAULT_VALUES, - shouldShowCredentialsForm: expect.any(Boolean), - activeApiTokenRawName: expect.any(String), - }; - - describe('activeApiTokenRawName', () => { - it('resets this value', () => { - mount({ - activeApiTokenRawName: 'foo', - }); - - CredentialsLogic.actions.hideCredentialsForm(); - expect(CredentialsLogic.values).toEqual({ - ...values, - activeApiTokenRawName: '', - }); - }); - }); - - describe('shouldShowCredentialsForm', () => { - it('resets this value', () => { - mount({ - shouldShowCredentialsForm: true, - }); - - CredentialsLogic.actions.hideCredentialsForm(); - expect(CredentialsLogic.values).toEqual({ - ...values, - shouldShowCredentialsForm: false, - }); - }); - }); - }); - - describe('resetCredentials', () => { - const values = { - ...DEFAULT_VALUES, - isCredentialsDetailsComplete: expect.any(Boolean), - isCredentialsDataComplete: expect.any(Boolean), - formErrors: expect.any(Array), - }; - - describe('isCredentialsDetailsComplete', () => { - it('should reset to false', () => { - mount({ - isCredentialsDetailsComplete: true, - }); - - CredentialsLogic.actions.resetCredentials(); - expect(CredentialsLogic.values).toEqual({ - ...values, - isCredentialsDetailsComplete: false, - }); - }); - }); - - describe('isCredentialsDataComplete', () => { - it('should reset to false', () => { - mount({ - isCredentialsDataComplete: true, - }); - - CredentialsLogic.actions.resetCredentials(); - expect(CredentialsLogic.values).toEqual({ - ...values, - isCredentialsDataComplete: false, - }); - }); - }); - - describe('formErrors', () => { - it('should reset', () => { - mount({ - formErrors: ['I am an error'], - }); - - CredentialsLogic.actions.resetCredentials(); - expect(CredentialsLogic.values).toEqual({ - ...values, - formErrors: [], - }); - }); - }); - }); - - describe('onPaginate', () => { - it('should set meta.page.current', () => { - mount({ meta: DEFAULT_META }); - - CredentialsLogic.actions.onPaginate(5); - expect(CredentialsLogic.values).toEqual({ - ...DEFAULT_VALUES, - meta: { - page: { - ...DEFAULT_META.page, - current: 5, - }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('fetchCredentials', () => { - const meta = { - page: { - current: 1, - size: 1, - total_pages: 1, - total_results: 1, - }, - }; - const results: object[] = []; - - it('will call an API endpoint and set the results with the `setCredentialsData` action', async () => { - mount(); - jest.spyOn(CredentialsLogic.actions, 'setCredentialsData').mockImplementationOnce(() => {}); - http.get.mockReturnValue(Promise.resolve({ meta, results })); - - CredentialsLogic.actions.fetchCredentials(); - expect(http.get).toHaveBeenCalledWith('/internal/app_search/credentials', { - query: { - 'page[current]': 1, - 'page[size]': 10, - }, - }); - await nextTick(); - expect(CredentialsLogic.actions.setCredentialsData).toHaveBeenCalledWith(meta, results); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - CredentialsLogic.actions.fetchCredentials(); - }); - }); - - describe('fetchDetails', () => { - it('will call an API endpoint and set the results with the `setCredentialsDetails` action', async () => { - mount(); - jest - .spyOn(CredentialsLogic.actions, 'setCredentialsDetails') - .mockImplementationOnce(() => {}); - http.get.mockReturnValue(Promise.resolve(credentialsDetails)); - - CredentialsLogic.actions.fetchDetails(); - expect(http.get).toHaveBeenCalledWith('/internal/app_search/credentials/details'); - await nextTick(); - expect(CredentialsLogic.actions.setCredentialsDetails).toHaveBeenCalledWith( - credentialsDetails - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - CredentialsLogic.actions.fetchDetails(); - }); - }); - - describe('deleteApiKey', () => { - const tokenName = 'abc123'; - - it('will call an API endpoint and re-fetch the credentials list', async () => { - mount(); - jest.spyOn(CredentialsLogic.actions, 'fetchCredentials').mockImplementationOnce(() => {}); - http.delete.mockReturnValue(Promise.resolve()); - - CredentialsLogic.actions.deleteApiKey(tokenName); - expect(http.delete).toHaveBeenCalledWith(`/internal/app_search/credentials/${tokenName}`); - await nextTick(); - - expect(CredentialsLogic.actions.fetchCredentials).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - - itShowsServerErrorAsFlashMessage(http.delete, () => { - mount(); - CredentialsLogic.actions.deleteApiKey(tokenName); - }); - }); - - describe('onApiTokenChange', () => { - it('calls a POST API endpoint that creates a new token if the active token does not exist yet', async () => { - const createdToken = { - name: 'new-key', - type: ApiTokenTypes.Admin, - }; - mount({ - activeApiToken: createdToken, - }); - jest.spyOn(CredentialsLogic.actions, 'onApiTokenCreateSuccess'); - http.post.mockReturnValue(Promise.resolve(createdToken)); - - CredentialsLogic.actions.onApiTokenChange(); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/credentials', { - body: JSON.stringify(createdToken), - }); - await nextTick(); - expect(CredentialsLogic.actions.onApiTokenCreateSuccess).toHaveBeenCalledWith(createdToken); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - - it('calls a PUT endpoint that updates the active token if it already exists', async () => { - const updatedToken = { - name: 'test-key', - type: ApiTokenTypes.Private, - read: true, - write: false, - access_all_engines: false, - engines: ['engine1'], - }; - mount({ - activeApiToken: { - ...updatedToken, - id: 'some-id', - }, - }); - jest.spyOn(CredentialsLogic.actions, 'onApiTokenUpdateSuccess'); - http.put.mockReturnValue(Promise.resolve(updatedToken)); - - CredentialsLogic.actions.onApiTokenChange(); - expect(http.put).toHaveBeenCalledWith('/internal/app_search/credentials/test-key', { - body: JSON.stringify(updatedToken), - }); - await nextTick(); - expect(CredentialsLogic.actions.onApiTokenUpdateSuccess).toHaveBeenCalledWith(updatedToken); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - - itShowsServerErrorAsFlashMessage(http.post, () => { - mount(); - CredentialsLogic.actions.onApiTokenChange(); - }); - - describe('token type data', () => { - it('does not send extra read/write/engine access data for admin tokens', () => { - const correctAdminToken = { - name: 'bogus-admin', - type: ApiTokenTypes.Admin, - }; - const extraData = { - read: true, - write: true, - access_all_engines: true, - }; - mount({ activeApiToken: { ...correctAdminToken, ...extraData } }); - - CredentialsLogic.actions.onApiTokenChange(); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/credentials', { - body: JSON.stringify(correctAdminToken), - }); - }); - - it('does not send extra read/write access data for search tokens', () => { - const correctSearchToken = { - name: 'bogus-search', - type: ApiTokenTypes.Search, - access_all_engines: false, - engines: ['some-engine'], - }; - const extraData = { - read: true, - write: false, - }; - mount({ activeApiToken: { ...correctSearchToken, ...extraData } }); - - CredentialsLogic.actions.onApiTokenChange(); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/credentials', { - body: JSON.stringify(correctSearchToken), - }); - }); - - // Private tokens send all data per the PUT test above. - // If that ever changes, we should capture that in another test here. - }); - }); - - describe('onEngineSelect', () => { - it('calls addEngineName if the engine is not selected', () => { - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - engines: [], - }, - }); - jest.spyOn(CredentialsLogic.actions, 'addEngineName'); - - CredentialsLogic.actions.onEngineSelect('engine1'); - expect(CredentialsLogic.actions.addEngineName).toHaveBeenCalledWith('engine1'); - expect(CredentialsLogic.values.activeApiToken.engines).toEqual(['engine1']); - }); - - it('calls removeEngineName if the engine is already selected', () => { - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - engines: ['engine1', 'engine2'], - }, - }); - jest.spyOn(CredentialsLogic.actions, 'removeEngineName'); - - CredentialsLogic.actions.onEngineSelect('engine1'); - expect(CredentialsLogic.actions.removeEngineName).toHaveBeenCalledWith('engine1'); - expect(CredentialsLogic.values.activeApiToken.engines).toEqual(['engine2']); - }); - }); - }); - - describe('selectors', () => { - describe('fullEngineAccessChecked', () => { - it('should be true if active token is set to access all engines and the user can access all engines', () => { - (AppLogic.selectors.myRole as jest.Mock).mockReturnValueOnce({ - canAccessAllEngines: true, - }); - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - access_all_engines: true, - }, - }); - - expect(CredentialsLogic.values.fullEngineAccessChecked).toEqual(true); - }); - - it('should be false if the token is not set to access all engines', () => { - (AppLogic.selectors.myRole as jest.Mock).mockReturnValueOnce({ - canAccessAllEngines: true, - }); - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - access_all_engines: false, - }, - }); - - expect(CredentialsLogic.values.fullEngineAccessChecked).toEqual(false); - }); - - it('should be false if the user cannot acess all engines', () => { - (AppLogic.selectors.myRole as jest.Mock).mockReturnValueOnce({ - canAccessAllEngines: false, - }); - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - access_all_engines: true, - }, - }); - - expect(CredentialsLogic.values.fullEngineAccessChecked).toEqual(false); - }); - }); - - describe('activeApiTokenExists', () => { - it('should be false if the token has no id', () => { - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - id: undefined, - }, - }); - - expect(CredentialsLogic.values.activeApiTokenExists).toEqual(false); - }); - - it('should be true if the token has an id', () => { - mount({ - activeApiToken: { - ...DEFAULT_VALUES.activeApiToken, - id: '123', - }, - }); - - expect(CredentialsLogic.values.activeApiTokenExists).toEqual(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts deleted file mode 100644 index 0c537016904f4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts +++ /dev/null @@ -1,336 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; -import { DEFAULT_META } from '../../../shared/constants'; -import { - clearFlashMessages, - flashSuccessToast, - flashAPIErrors, -} from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { updateMetaPageIndex } from '../../../shared/table_pagination'; - -import { AppLogic } from '../../app_logic'; -import { Engine } from '../../types'; -import { formatApiName } from '../../utils/format_api_name'; - -import { ApiTokenTypes, CREATE_MESSAGE, UPDATE_MESSAGE, DELETE_MESSAGE } from './constants'; - -import { ApiToken, CredentialsDetails, TokenReadWrite } from './types'; - -export const defaultApiToken: ApiToken = { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, -}; - -interface CredentialsLogicActions { - addEngineName(engineName: string): string; - onApiTokenCreateSuccess(apiToken: ApiToken): ApiToken; - onApiTokenError(formErrors: string[]): string[]; - onApiTokenUpdateSuccess(apiToken: ApiToken): ApiToken; - removeEngineName(engineName: string): string; - setAccessAllEngines(accessAll: boolean): boolean; - setCredentialsData(meta: Meta, apiTokens: ApiToken[]): { meta: Meta; apiTokens: ApiToken[] }; - setCredentialsDetails(details: CredentialsDetails): CredentialsDetails; - setNameInputBlurred(isBlurred: boolean): boolean; - setTokenReadWrite(tokenReadWrite: TokenReadWrite): TokenReadWrite; - setTokenName(name: string): string; - setTokenType(tokenType: string): string; - showCredentialsForm(apiToken?: ApiToken): ApiToken; - hideCredentialsForm(): { value: boolean }; - resetCredentials(): { value: boolean }; - onPaginate(newPageIndex: number): { newPageIndex: number }; - fetchCredentials(): void; - fetchDetails(): { value: boolean }; - deleteApiKey(tokenName: string): string; - onApiTokenChange(): void; - onEngineSelect(engineName: string): string; -} - -interface CredentialsLogicValues { - activeApiToken: ApiToken; - activeApiTokenExists: boolean; - activeApiTokenRawName: string; - apiTokens: ApiToken[]; - dataLoading: boolean; - engines: Engine[]; - formErrors: string[]; - isCredentialsDataComplete: boolean; - isCredentialsDetailsComplete: boolean; - fullEngineAccessChecked: boolean; - meta: Meta; - nameInputBlurred: boolean; - shouldShowCredentialsForm: boolean; -} - -type CredentialsLogicType = MakeLogicType; // If we leave this inline, Prettier does some horrifying indenting nonsense :/ - -export const CredentialsLogic = kea({ - path: ['enterprise_search', 'app_search', 'credentials_logic'], - actions: () => ({ - addEngineName: (engineName) => engineName, - onApiTokenCreateSuccess: (apiToken) => apiToken, - onApiTokenError: (formErrors) => formErrors, - onApiTokenUpdateSuccess: (apiToken) => apiToken, - removeEngineName: (engineName) => engineName, - setAccessAllEngines: (accessAll) => accessAll, - setCredentialsData: (meta, apiTokens) => ({ meta, apiTokens }), - setCredentialsDetails: (details) => details, - setNameInputBlurred: (nameInputBlurred) => nameInputBlurred, - setTokenReadWrite: ({ name, checked }) => ({ name, checked }), - setTokenName: (name) => name, - setTokenType: (tokenType) => tokenType, - showCredentialsForm: (apiToken = { ...defaultApiToken }) => apiToken, - hideCredentialsForm: false, - resetCredentials: false, - onPaginate: (newPageIndex) => ({ newPageIndex }), - fetchCredentials: true, - fetchDetails: true, - deleteApiKey: (tokenName) => tokenName, - onApiTokenChange: () => null, - onEngineSelect: (engineName) => engineName, - }), - reducers: () => ({ - apiTokens: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setCredentialsData: (_, { apiTokens }) => apiTokens, - // @ts-expect-error upgrade typescript v5.1.6 - onApiTokenCreateSuccess: (apiTokens, apiToken) => [...apiTokens, apiToken], - // @ts-expect-error upgrade typescript v5.1.6 - onApiTokenUpdateSuccess: (apiTokens, apiToken) => [ - // @ts-expect-error upgrade typescript v5.1.6 - ...apiTokens.filter((token) => token.name !== apiToken.name), - apiToken, - ], - }, - ], - meta: [ - DEFAULT_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - setCredentialsData: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - isCredentialsDetailsComplete: [ - false, - { - setCredentialsDetails: () => true, - resetCredentials: () => false, - }, - ], - isCredentialsDataComplete: [ - false, - { - setCredentialsData: () => true, - fetchCredentials: () => false, - resetCredentials: () => false, - }, - ], - engines: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setCredentialsDetails: (_, { engines }) => engines, - }, - ], - nameInputBlurred: [ - false, - { - // @ts-expect-error upgrade typescript v5.1.6 - setNameInputBlurred: (_, nameInputBlurred) => nameInputBlurred, - }, - ], - activeApiToken: [ - defaultApiToken, - { - // @ts-expect-error upgrade typescript v5.1.6 - addEngineName: (activeApiToken, engineName) => ({ - ...activeApiToken, - engines: [...(activeApiToken.engines || []), engineName], - }), - // @ts-expect-error upgrade typescript v5.1.6 - removeEngineName: (activeApiToken, engineName) => ({ - ...activeApiToken, - // @ts-expect-error upgrade typescript v5.1.6 - engines: (activeApiToken.engines || []).filter((name) => name !== engineName), - }), - // @ts-expect-error upgrade typescript v5.1.6 - setAccessAllEngines: (activeApiToken, accessAll) => ({ - ...activeApiToken, - access_all_engines: accessAll, - engines: accessAll ? [] : activeApiToken.engines, - }), - onApiTokenCreateSuccess: () => defaultApiToken, - onApiTokenUpdateSuccess: () => defaultApiToken, - // @ts-expect-error upgrade typescript v5.1.6 - setTokenName: (activeApiToken, name) => ({ ...activeApiToken, name: formatApiName(name) }), - // @ts-expect-error upgrade typescript v5.1.6 - setTokenReadWrite: (activeApiToken, { name, checked }) => ({ - ...activeApiToken, - [name]: checked, - }), - // @ts-expect-error upgrade typescript v5.1.6 - setTokenType: (activeApiToken, tokenType) => ({ - ...activeApiToken, - access_all_engines: - tokenType === ApiTokenTypes.Admin ? false : activeApiToken.access_all_engines, - engines: tokenType === ApiTokenTypes.Admin ? [] : activeApiToken.engines, - write: tokenType === ApiTokenTypes.Private, - read: tokenType === ApiTokenTypes.Private, - type: tokenType as ApiTokenTypes, - }), - // @ts-expect-error upgrade typescript v5.1.6 - showCredentialsForm: (_, activeApiToken) => activeApiToken, - }, - ], - activeApiTokenRawName: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setTokenName: (_, activeApiTokenRawName) => activeApiTokenRawName, - // @ts-expect-error upgrade typescript v5.1.6 - showCredentialsForm: (_, activeApiToken) => activeApiToken.name, - hideCredentialsForm: () => '', - onApiTokenCreateSuccess: () => '', - onApiTokenUpdateSuccess: () => '', - }, - ], - shouldShowCredentialsForm: [ - false, - { - showCredentialsForm: () => true, - hideCredentialsForm: () => false, - onApiTokenCreateSuccess: () => false, - onApiTokenUpdateSuccess: () => false, - }, - ], - formErrors: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onApiTokenError: (_, formErrors) => formErrors, - onApiTokenCreateSuccess: () => [], - showCredentialsForm: () => [], - resetCredentials: () => [], - }, - ], - }), - selectors: ({ selectors }) => ({ - fullEngineAccessChecked: [ - () => [AppLogic.selectors.myRole, selectors.activeApiToken], - (myRole, activeApiToken) => - !!(myRole.canAccessAllEngines && activeApiToken.access_all_engines), - ], - dataLoading: [ - () => [selectors.isCredentialsDetailsComplete, selectors.isCredentialsDataComplete], - (isCredentialsDetailsComplete, isCredentialsDataComplete) => { - return isCredentialsDetailsComplete === false && isCredentialsDataComplete === false; - }, - ], - activeApiTokenExists: [ - () => [selectors.activeApiToken], - (activeApiToken) => !!activeApiToken.id, - ], - }), - listeners: ({ actions, values }) => ({ - showCredentialsForm: () => { - clearFlashMessages(); - }, - fetchCredentials: async () => { - try { - const { http } = HttpLogic.values; - const { meta } = values; - const query = { - 'page[current]': meta.page.current, - 'page[size]': meta.page.size, - }; - const response = await http.get<{ meta: Meta; results: ApiToken[] }>( - '/internal/app_search/credentials', - { query } - ); - actions.setCredentialsData(response.meta, response.results); - } catch (e) { - flashAPIErrors(e); - } - }, - fetchDetails: async () => { - try { - const { http } = HttpLogic.values; - const response = await http.get( - '/internal/app_search/credentials/details' - ); - - actions.setCredentialsDetails(response); - } catch (e) { - flashAPIErrors(e); - } - }, - deleteApiKey: async (tokenName) => { - try { - const { http } = HttpLogic.values; - await http.delete(`/internal/app_search/credentials/${tokenName}`); - - actions.fetchCredentials(); - flashSuccessToast(DELETE_MESSAGE(tokenName)); - } catch (e) { - flashAPIErrors(e); - } - }, - onApiTokenChange: async () => { - const { id, name, engines, type, read, write } = values.activeApiToken; - - const data: ApiToken = { - name, - type, - }; - if (type === ApiTokenTypes.Private) { - data.read = read; - data.write = write; - } - if (type !== ApiTokenTypes.Admin) { - data.access_all_engines = values.fullEngineAccessChecked; - data.engines = engines; - } - - try { - const { http } = HttpLogic.values; - const body = JSON.stringify(data); - - if (id) { - const response = await http.put(`/internal/app_search/credentials/${name}`, { - body, - }); - actions.onApiTokenUpdateSuccess(response); - flashSuccessToast(UPDATE_MESSAGE(name)); - } else { - const response = await http.post('/internal/app_search/credentials', { body }); - actions.onApiTokenCreateSuccess(response); - flashSuccessToast(CREATE_MESSAGE(name)); - } - } catch (e) { - flashAPIErrors(e); - } - }, - onEngineSelect: (engineName: string) => { - if (values.activeApiToken?.engines?.includes(engineName)) { - actions.removeEngineName(engineName); - } else { - actions.addEngineName(engineName); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts deleted file mode 100644 index 75535ca6b6843..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { Credentials } from './credentials'; -export { CREDENTIALS_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts deleted file mode 100644 index 0427d25add49b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { Engine } from '../../types'; - -import { ApiTokenTypes } from './constants'; - -export interface CredentialsDetails { - engines: Engine[]; -} - -export interface ApiToken { - access_all_engines?: boolean; - key?: string; - engines?: string[]; - id?: number; - name: string; - read?: boolean; - type: ApiTokenTypes; - write?: boolean; -} - -export interface TokenReadWrite { - name: 'read' | 'write'; - checked: boolean; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts deleted file mode 100644 index 70277d6cb7f22..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { ApiTokenTypes } from '../constants'; - -import { ApiToken } from '../types'; - -import { apiTokenSort } from '.'; - -describe('apiTokenSort', () => { - const apiToken: ApiToken = { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - key: 'abc-1234', - }; - - it('sorts items by id', () => { - const apiTokens = [ - { - ...apiToken, - id: 2, - }, - { - ...apiToken, - id: undefined, - }, - { - ...apiToken, - id: 1, - }, - ]; - - expect(apiTokens.sort(apiTokenSort).map((t) => t.id)).toEqual([undefined, 1, 2]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts deleted file mode 100644 index eb4332dae7aaf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 { ApiToken } from '../types'; - -export const apiTokenSort = (apiTokenA: ApiToken, apiTokenB: ApiToken): number => { - if (!apiTokenA.id) { - return -1; - } - if (!apiTokenB.id) { - return 1; - } - return apiTokenA.id - apiTokenB.id; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx deleted file mode 100644 index 71d00efa2a868..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 { ApiTokenTypes } from '../constants'; -import { ApiToken } from '../types'; - -import { getEnginesDisplayText } from './get_engines_display_text'; - -describe('getEnginesDisplayText', () => { - const apiToken: ApiToken = { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - engines: ['engine1', 'engine2', 'engine3'], - }; - - it('returns "--" when the token is an admin token', () => { - const wrapper = shallow( -
{getEnginesDisplayText({ ...apiToken, type: ApiTokenTypes.Admin })}
- ); - expect(wrapper.text()).toEqual('--'); - }); - - it('returns "all" when access_all_engines is true', () => { - const wrapper = shallow( -
{getEnginesDisplayText({ ...apiToken, access_all_engines: true })}
- ); - expect(wrapper.text()).toEqual('all'); - }); - - it('returns a list of engines if access_all_engines is false', () => { - const wrapper = shallow( -
{getEnginesDisplayText({ ...apiToken, access_all_engines: false })}
- ); - - expect(wrapper.find('li').map((e) => e.text())).toEqual(['engine1', 'engine2', 'engine3']); - }); - - it('returns "--" when the token is an admin token, even if access_all_engines is true', () => { - const wrapper = shallow( -
- {getEnginesDisplayText({ - ...apiToken, - access_all_engines: true, - type: ApiTokenTypes.Admin, - })} -
- ); - expect(wrapper.text()).toEqual('--'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx deleted file mode 100644 index d3577ec14fec9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { ApiTokenTypes, ALL } from '../constants'; -import { ApiToken } from '../types'; - -export const getEnginesDisplayText = (apiToken: ApiToken): JSX.Element | string => { - const { type, access_all_engines: accessAll, engines = [] } = apiToken; - if (type === ApiTokenTypes.Admin) { - return '--'; - } - if (accessAll) { - return ALL; - } - return ( -
    - {engines.map((engine) => ( -
  • {engine}
  • - ))} -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts deleted file mode 100644 index 4c026aebc1b13..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { ApiTokenTypes } from '../constants'; -import { ApiToken } from '../types'; - -import { getModeDisplayText } from './get_mode_display_text'; - -const apiToken: ApiToken = { - name: '', - type: ApiTokenTypes.Private, - read: true, - write: true, - access_all_engines: true, - engines: ['engine1', 'engine2', 'engine3'], -}; - -describe('getModeDisplayText', () => { - it('will return read/write when read and write are enabled', () => { - expect(getModeDisplayText({ ...apiToken, read: true, write: true })).toEqual('read/write'); - }); - - it('will return read-only when only read is enabled', () => { - expect(getModeDisplayText({ ...apiToken, read: true, write: false })).toEqual('read-only'); - }); - - it('will return write-only when only write is enabled', () => { - expect(getModeDisplayText({ ...apiToken, read: false, write: true })).toEqual('write-only'); - }); - - it('will return "search" if the key is a search key, regardless of read/write state', () => { - expect( - getModeDisplayText({ ...apiToken, type: ApiTokenTypes.Search, read: false, write: true }) - ).toEqual('search'); - }); - - it('will return "--" if the key is an admin key, regardless of read/write state', () => { - expect( - getModeDisplayText({ ...apiToken, type: ApiTokenTypes.Admin, read: false, write: true }) - ).toEqual('--'); - }); - - it('will default read and write to false', () => { - expect( - getModeDisplayText({ - name: 'test', - type: ApiTokenTypes.Private, - }) - ).toEqual('read-only'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts deleted file mode 100644 index 97c1d3fad33a8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_mode_display_text.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { ApiTokenTypes, READ_ONLY, READ_WRITE, SEARCH_DISPLAY, WRITE_ONLY } from '../constants'; -import { ApiToken } from '../types'; - -export const getModeDisplayText = (apiToken: ApiToken): string => { - const { read = false, write = false, type } = apiToken; - - switch (type) { - case ApiTokenTypes.Admin: - return '--'; - case ApiTokenTypes.Search: - return SEARCH_DISPLAY; - default: - if (read && write) { - return READ_WRITE; - } - return write ? WRITE_ONLY : READ_ONLY; - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/index.ts deleted file mode 100644 index 2e538ee450352..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { apiTokenSort } from './api_token_sort'; -export { getEnginesDisplayText } from './get_engines_display_text'; -export { getModeDisplayText } from './get_mode_display_text'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/automated_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/automated_icon.test.tsx deleted file mode 100644 index c9be6e609f37c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/automated_icon.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 { AutomatedIcon } from './automated_icon'; - -describe('AutomatedIcon', () => { - it('renders', () => { - expect(shallow().is('svg')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/automated_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/automated_icon.tsx deleted file mode 100644 index d50cf101e6059..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/automated_icon.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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'; - -export const AutomatedIcon: React.FC = ({ ...props }) => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.scss deleted file mode 100644 index cdff5042f89b7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.scss +++ /dev/null @@ -1,5 +0,0 @@ -.curationsTable { - .curationsTableBadge { - margin-left: $euiSizeS; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.test.tsx deleted file mode 100644 index d86cb8592635a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 { mockKibanaValues, setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ReactWrapper } from 'enzyme'; - -import { EuiBadge, EuiBasicTable } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { CurationsTable } from './curations_table'; - -describe('CurationsTable', () => { - const { navigateToUrl } = mockKibanaValues; - - const values = { - dataLoading: false, - curations: [ - { - id: 'cur-id-1', - last_updated: 'January 1, 1970 at 12:00PM', - queries: ['hiking'], - suggestion: { - status: 'automated', - }, - }, - { - id: 'cur-id-2', - last_updated: 'January 2, 1970 at 12:00PM', - queries: ['mountains', 'valleys'], - suggestion: { - status: 'pending', - }, - }, - ], - meta: { - page: { - current: 1, - size: 10, - total_results: 2, - }, - }, - }; - - const actions = { - deleteCuration: jest.fn(), - onPaginate: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('passes loading prop based on dataLoading', () => { - setMockValues({ ...values, dataLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true); - }); - - describe('populated table render', () => { - let wrapper: ReactWrapper; - - beforeAll(() => { - wrapper = mountWithIntl(); - }); - - it('renders queries and last updated columns', () => { - const tableContent = wrapper.find(EuiBasicTable).text(); - - expect(tableContent).toContain('Queries'); - expect(tableContent).toContain('hiking'); - expect(tableContent).toContain('mountains, valleys'); - - expect(tableContent).toContain('Last updated'); - expect(tableContent).toContain('Jan 1, 1970 12:00 PM'); - expect(tableContent).toContain('Jan 2, 1970 12:00 PM'); - }); - - it('renders queries with curation links and curation suggestion badges', () => { - const firstQueryLink = wrapper - .find('EuiLinkTo[data-test-subj="CurationsTableQueriesLink"]') - .first(); - const secondQueryLink = wrapper - .find('EuiLinkTo[data-test-subj="CurationsTableQueriesLink"]') - .last(); - - expect(firstQueryLink.prop('to')).toEqual('/engines/some-engine/curations/cur-id-1'); - expect(firstQueryLink.find(EuiBadge).prop('children')).toEqual('Automated'); - expect(secondQueryLink.prop('to')).toEqual('/engines/some-engine/curations/cur-id-2'); - expect(secondQueryLink.find(EuiBadge).prop('children')).toEqual('New suggestion'); - }); - - describe('action column', () => { - it('edit action navigates to curation link', () => { - wrapper.find('[data-test-subj="CurationsTableEditButton"]').first().simulate('click'); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-id-1'); - - wrapper.find('[data-test-subj="CurationsTableEditButton"]').last().simulate('click'); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-id-2'); - }); - - it('delete action calls deleteCuration', () => { - wrapper.find('[data-test-subj="CurationsTableDeleteButton"]').first().simulate('click'); - expect(actions.deleteCuration).toHaveBeenCalledWith('cur-id-1'); - - wrapper.find('[data-test-subj="CurationsTableDeleteButton"]').last().simulate('click'); - expect(actions.deleteCuration).toHaveBeenCalledWith('cur-id-2'); - }); - }); - }); - - describe('pagination', () => { - it('passes pagination props from meta.page', () => { - setMockValues({ - ...values, - meta: { - page: { - current: 5, - size: 10, - total_results: 50, - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiBasicTable).prop('pagination')).toEqual({ - pageIndex: 4, - pageSize: 10, - totalItemCount: 50, - showPerPageOptions: false, - }); - }); - - it('calls onPaginate on pagination change', () => { - const wrapper = shallow(); - wrapper.find(EuiBasicTable).simulate('change', { page: { index: 0 } }); - - expect(actions.onPaginate).toHaveBeenCalledWith(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.tsx deleted file mode 100644 index 31897bf9ec8c8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/curations_table.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiBadge, EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EDIT_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants'; -import { KibanaLogic } from '../../../../shared/kibana'; -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; -import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; - -import { ENGINE_CURATION_PATH } from '../../../routes'; -import { FormattedDateTime } from '../../../utils/formatted_date_time'; -import { DataPanel } from '../../data_panel'; -import { generateEnginePath } from '../../engine'; - -import { CurationsLogic } from '../curations_logic'; -import { Curation } from '../types'; -import { convertToDate } from '../utils'; - -import { AutomatedIcon } from './automated_icon'; - -import './curations_table.scss'; - -export const CurationsTable: React.FC = () => { - const { dataLoading, curations, meta } = useValues(CurationsLogic); - const { onPaginate, deleteCuration } = useActions(CurationsLogic); - - const columns: Array> = [ - { - field: 'queries', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.table.column.queries', - { defaultMessage: 'Queries' } - ), - render: (queries: Curation['queries'], curation: Curation) => ( - - {queries.join(', ')} - {curation.suggestion?.status === 'automated' && ( - <> - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.table.automatedLabel', - { defaultMessage: 'Automated' } - )} - - - )} - {curation.suggestion?.status === 'pending' && ( - <> - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.table.newSuggestionLabel', - { defaultMessage: 'New suggestion' } - )} - - - )} - - ), - width: '40%', - truncateText: true, - mobileOptions: { - header: true, - enlarge: true, - width: '100%', - }, - }, - { - field: 'last_updated', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.table.column.lastUpdated', - { defaultMessage: 'Last updated' } - ), - width: '30%', - dataType: 'string', - render: (dateString: string) => , - }, - { - width: '120px', - actions: [ - { - name: EDIT_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.table.editTooltip', - { defaultMessage: 'Edit curation' } - ), - type: 'icon', - icon: 'pencil', - color: 'primary', - onClick: (curation: Curation) => { - const { navigateToUrl } = KibanaLogic.values; - const url = generateEnginePath(ENGINE_CURATION_PATH, { curationId: curation.id }); - navigateToUrl(url); - }, - 'data-test-subj': 'CurationsTableEditButton', - }, - { - name: DELETE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.table.deleteTooltip', - { defaultMessage: 'Delete curation' } - ), - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (curation: Curation) => deleteCuration(curation.id), - 'data-test-subj': 'CurationsTableDeleteButton', - }, - ], - }, - ]; - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.table.title', { - defaultMessage: 'Active curations', - })} - - } - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx deleted file mode 100644 index 7608e4d0861f9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Create your first curation'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchCurations) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx deleted file mode 100644 index 363da83d56aac..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyState: React.FC = () => ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.empty.noCurationsTitle', - { defaultMessage: 'Create your first curation' } - )} - - } - body={ -

- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.empty.description', { - defaultMessage: - 'Use curations to promote and hide documents. Help people discover what you would most like them to discover.', - })} -

- } - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel', { - defaultMessage: 'Read the curations guide', - })} - - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/index.ts deleted file mode 100644 index 473162dcbd91e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { CurationsTable } from './curations_table'; -export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.test.tsx deleted file mode 100644 index 1732b6f746691..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 '../../../../__mocks__/react_router'; - -jest.mock('../../../../shared/use_local_storage', () => ({ - useLocalStorage: jest.fn(), -})); - -import React from 'react'; - -import { useLocation } from 'react-router-dom'; - -import { shallow } from 'enzyme'; - -import { EuiButtonEmpty, EuiCallOut } from '@elastic/eui'; - -import { EuiButtonTo } from '../../../../shared/react_router_helpers'; -import { useLocalStorage } from '../../../../shared/use_local_storage'; - -import { SuggestionsCallout } from './suggestions_callout'; - -const props = { - title: 'Title', - description: 'A description.', - buttonTo: '/suggestions', -}; - -const now = '2021-01-01T00:30:00Z'; -const tenMinutesAgo = '2021-01-01T00:20:00Z'; -const twentyMinutesAgo = '2021-01-01T00:10:00Z'; - -describe('SuggestionsCallout', () => { - const mockSetLastDismissedTimestamp = jest.fn(); - const setMockLastDismissedTimestamp = (lastDismissedTimestamp: string) => { - (useLocalStorage as jest.Mock).mockImplementation(() => [ - lastDismissedTimestamp, - mockSetLastDismissedTimestamp, - ]); - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockLastDismissedTimestamp(tenMinutesAgo); - (useLocation as jest.Mock).mockImplementationOnce(() => ({ - pathname: '/engines/some-engine', - })); - }); - - it('renders a callout with a link to the suggestions', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)); - expect(wrapper.find(EuiButtonTo).prop('to')).toEqual('/suggestions'); - }); - - it('is empty is it was updated before it was last dismissed', () => { - const wrapper = shallow( - - ); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('clicking the dismiss button updates the timestamp in local storage', () => { - jest.spyOn(global.Date.prototype, 'toISOString').mockImplementation(() => now); - - const wrapper = shallow(); - wrapper.find(EuiButtonEmpty).simulate('click'); - - expect(mockSetLastDismissedTimestamp).toHaveBeenCalledWith(now); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx deleted file mode 100644 index ed46a878f0cea..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_callout.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { useLocation } from 'react-router-dom'; - -import { - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { LightbulbIcon } from '../../../../shared/icons'; -import { EuiButtonTo } from '../../../../shared/react_router_helpers'; -import { useLocalStorage } from '../../../../shared/use_local_storage'; - -interface SuggestionsCalloutProps { - title: string; - description: string; - buttonTo: string; - lastUpdatedTimestamp: string; // ISO string like '2021-10-04T18:53:02.784Z' - style?: React.CSSProperties; -} - -export const SuggestionsCallout: React.FC = ({ - title, - description, - buttonTo, - lastUpdatedTimestamp, - style, -}) => { - const { pathname } = useLocation(); - - const [lastDismissedTimestamp, setLastDismissedTimestamp] = useLocalStorage( - `suggestions-callout--${pathname}`, - new Date(0).toISOString() - ); - - if (new Date(lastDismissedTimestamp) >= new Date(lastUpdatedTimestamp)) { - return null; - } - - return ( - <> - - -

{description}

-
- - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.reviewSuggestionsButtonLabel', - { defaultMessage: 'Review suggestions' } - )} - - - - { - setLastDismissedTimestamp(new Date().toISOString()); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsCallout.hideForNowLabel', - { defaultMessage: 'Hide this for now' } - )} - - - -
- - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx deleted file mode 100644 index db9ee7a4bd12b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { DEFAULT_META } from '../../../../shared/constants'; - -import { itShowsServerErrorAsFlashMessage } from '../../../../test_helpers'; - -import { SuggestionsAPIResponse, SuggestionsLogic } from './suggestions_logic'; - -const DEFAULT_VALUES = { - dataLoading: true, - suggestions: [], - meta: { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - size: 10, - }, - }, -}; - -const MOCK_RESPONSE: SuggestionsAPIResponse = { - meta: { - page: { - current: 1, - size: 10, - total_results: 1, - total_pages: 1, - }, - }, - results: [ - { - query: 'foo', - updated_at: '2021-07-08T14:35:50Z', - promoted: ['1', '2'], - status: 'applied', - operation: 'create', - }, - ], -}; - -describe('SuggestionsLogic', () => { - const { mount } = new LogicMounter(SuggestionsLogic); - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(SuggestionsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onSuggestionsLoaded', () => { - it('should set suggestion, meta state, & dataLoading to false', () => { - mount(); - - SuggestionsLogic.actions.onSuggestionsLoaded(MOCK_RESPONSE); - - expect(SuggestionsLogic.values).toEqual({ - ...DEFAULT_VALUES, - suggestions: MOCK_RESPONSE.results, - meta: MOCK_RESPONSE.meta, - dataLoading: false, - }); - }); - }); - - describe('onPaginate', () => { - it('should update meta', () => { - mount(); - - SuggestionsLogic.actions.onPaginate(2); - - expect(SuggestionsLogic.values).toEqual({ - ...DEFAULT_VALUES, - meta: { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - current: 2, - }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadSuggestions', () => { - it('should set dataLoading state', () => { - mount({ dataLoading: false }); - - SuggestionsLogic.actions.loadSuggestions(); - - expect(SuggestionsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and set suggestions & meta state', async () => { - http.post.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mount(); - jest.spyOn(SuggestionsLogic.actions, 'onSuggestionsLoaded'); - - SuggestionsLogic.actions.loadSuggestions(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify({ - page: { - current: 1, - size: 10, - }, - filters: { - status: ['pending'], - type: 'curation', - }, - }), - } - ); - - expect(SuggestionsLogic.actions.onSuggestionsLoaded).toHaveBeenCalledWith(MOCK_RESPONSE); - }); - - itShowsServerErrorAsFlashMessage(http.post, () => { - mount(); - SuggestionsLogic.actions.loadSuggestions(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx deleted file mode 100644 index efffbc0f5e6c8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 { Meta } from '../../../../../../common/types'; -import { DEFAULT_META } from '../../../../shared/constants'; -import { flashAPIErrors } from '../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../shared/http'; -import { updateMetaPageIndex } from '../../../../shared/table_pagination'; -import { EngineLogic } from '../../engine'; -import { CurationSuggestion } from '../types'; - -export interface SuggestionsAPIResponse { - results: CurationSuggestion[]; - meta: Meta; -} - -interface SuggestionsValues { - dataLoading: boolean; - suggestions: CurationSuggestion[]; - meta: Meta; -} - -interface SuggestionActions { - loadSuggestions(): void; - onPaginate(newPageIndex: number): { newPageIndex: number }; - onSuggestionsLoaded(response: SuggestionsAPIResponse): SuggestionsAPIResponse; -} - -export const SuggestionsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'curations', 'suggestions_logic'], - actions: () => ({ - onPaginate: (newPageIndex) => ({ newPageIndex }), - onSuggestionsLoaded: ({ results, meta }) => ({ results, meta }), - loadSuggestions: true, - }), - reducers: () => ({ - dataLoading: [ - true, - { - loadSuggestions: () => true, - onSuggestionsLoaded: () => false, - }, - ], - suggestions: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onSuggestionsLoaded: (_, { results }) => results, - }, - ], - meta: [ - { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - size: 10, - }, - }, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSuggestionsLoaded: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - }), - listeners: ({ actions, values }) => ({ - loadSuggestions: async () => { - const { meta } = values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.post( - `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, - { - body: JSON.stringify({ - page: { - current: meta.page.current, - size: meta.page.size, - }, - filters: { - status: ['pending'], - type: 'curation', - }, - }), - } - ); - actions.onSuggestionsLoaded(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.scss deleted file mode 100644 index 9c88bc1e3dc03..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.scss +++ /dev/null @@ -1,5 +0,0 @@ -.suggestionsTable { - .suggestionsTableBadge { - margin-left: $euiSizeS; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.test.tsx deleted file mode 100644 index 439f9dabadee6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { mockKibanaValues, setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiBadge, EuiBasicTable } from '@elastic/eui'; - -import { SuggestionsTable } from './suggestions_table'; - -describe('SuggestionsTable', () => { - const { navigateToUrl } = mockKibanaValues; - - const values = { - engineName: 'some-engine', - dataLoading: false, - suggestions: [ - { - query: 'foo', - updated_at: '2021-07-08T14:35:50Z', - promoted: ['1', '2'], - }, - ], - meta: { - page: { - current: 1, - size: 10, - total_results: 2, - }, - }, - }; - - const mockActions = { - loadSuggestions: jest.fn(), - onPaginate: jest.fn(), - }; - - beforeAll(() => { - setMockValues(values); - setMockActions(mockActions); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const getColumn = (index: number) => { - const wrapper = shallow(); - const table = wrapper.find(EuiBasicTable); - const columns = table.prop('columns'); - return columns[index]; - }; - - const renderColumn = (index: number) => { - const column = getColumn(index); - // @ts-ignore - return (...props) => { - // @ts-ignore - return shallow(column.render(...props)); - }; - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiBasicTable).exists()).toBe(true); - }); - - it('show a suggestions query with a link', () => { - const wrapper = renderColumn(0)('test', {}); - expect(wrapper.prop('href')).toBe( - '/app/enterprise_search/engines/some-engine/curations/suggestions/test' - ); - expect(wrapper.text()).toEqual('test'); - }); - - it('show a badge when there are overrides', () => { - let wrapper: ShallowWrapper; - - wrapper = renderColumn(0)('test', {}); - expect(wrapper.find(EuiBadge)).toHaveLength(0); - - wrapper = renderColumn(0)('test', { override_manual_curation: true }); - expect(wrapper.find(EuiBadge).prop('children')).toEqual('Overrides'); - }); - - it('contains an updated at timestamp', () => { - const wrapper = renderColumn(1)('2021-07-08T14:35:50Z'); - expect(wrapper.find('FormattedDate').exists()).toBe(true); - }); - - it('contains a promoted documents count', () => { - const wrapper = renderColumn(2)(['a', 'b', 'c']); - expect(wrapper.text()).toEqual('3'); - }); - - it('has a view action', () => { - const column = getColumn(3); - // @ts-ignore - const actions = column.actions; - actions[0].onClick({ - query: 'foo', - }); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/suggestions/foo'); - }); - - it('fetches data on load', () => { - shallow(); - - expect(mockActions.loadSuggestions).toHaveBeenCalled(); - }); - - it('supports pagination', () => { - const wrapper = shallow(); - wrapper.find(EuiBasicTable).simulate('change', { page: { index: 0 } }); - - expect(mockActions.onPaginate).toHaveBeenCalledWith(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.tsx deleted file mode 100644 index 2ce376d2d34a3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_table.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiBadge, EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { VIEW_BUTTON_LABEL } from '../../../../shared/constants'; -import { LightbulbIcon } from '../../../../shared/icons'; -import { KibanaLogic } from '../../../../shared/kibana'; -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; -import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; -import { ENGINE_CURATION_SUGGESTION_PATH } from '../../../routes'; -import { FormattedDateTime } from '../../../utils/formatted_date_time'; -import { DataPanel } from '../../data_panel'; -import { generateEnginePath } from '../../engine'; -import { CurationSuggestion } from '../types'; -import { convertToDate } from '../utils'; - -import { SuggestionsLogic } from './suggestions_logic'; - -import './suggestions_table.scss'; - -const getSuggestionRoute = (query: string) => { - return generateEnginePath(ENGINE_CURATION_SUGGESTION_PATH, { query }); -}; - -const columns: Array> = [ - { - field: 'query', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.queryTableHeader', - { defaultMessage: 'Query' } - ), - render: (query: string, curation: CurationSuggestion) => ( - - {query} - {curation.override_manual_curation && ( - <> - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.overridesLabel', - { defaultMessage: 'Overrides' } - )} - - - )} - - ), - }, - { - field: 'updated_at', - dataType: 'string', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.lastUpdatedTableHeader', - { defaultMessage: 'Last updated' } - ), - render: (dateString: string) => , - }, - { - field: 'promoted', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.promotedDocumentsTableHeader', - { defaultMessage: 'Promoted results' } - ), - render: (promoted: string[]) => {promoted.length}, - }, - { - actions: [ - { - name: VIEW_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.viewTooltip', - { defaultMessage: 'View suggestion' } - ), - type: 'icon', - icon: 'eye', - onClick: (item) => { - const { navigateToUrl } = KibanaLogic.values; - const query = item.query; - navigateToUrl(getSuggestionRoute(query)); - }, - }, - ], - width: '120px', - }, -]; - -export const SuggestionsTable: React.FC = () => { - const { loadSuggestions, onPaginate } = useActions(SuggestionsLogic); - const { meta, suggestions, dataLoading } = useValues(SuggestionsLogic); - - useEffect(() => { - loadSuggestions(); - }, [meta.page.current]); - - const totalSuggestions = meta.page.total_results; - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.title', - { - defaultMessage: '{totalSuggestions} Suggestions', - values: { totalSuggestions }, - } - )} - - } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.description', - { - defaultMessage: - 'Based on your analytics, results for the following queries could be improved by promoting some documents.', - } - )} - hasBorder - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts deleted file mode 100644 index b70e654d5f34b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 { EuiButtonIconProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const CURATIONS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.title', - { defaultMessage: 'Curations' } -); -export const CURATIONS_OVERVIEW_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.overview.title', - { defaultMessage: 'Curated results' } -); -export const CREATE_NEW_CURATION_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.create.title', - { defaultMessage: 'Create a curation' } -); -export const MANAGE_CURATION_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.manage.title', - { defaultMessage: 'Manage curation' } -); - -export const QUERY_INPUTS_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.addQueryButtonLabel', - { defaultMessage: 'Add query' } -); -export const QUERY_INPUTS_PLACEHOLDER = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.queryPlaceholder', - { defaultMessage: 'Enter a query' } -); - -export const DELETE_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.deleteConfirmation', - { defaultMessage: 'Are you sure you want to remove this curation?' } -); -export const DELETE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage', - { defaultMessage: 'Your curation was deleted' } -); -export const RESTORE_CONFIRMATION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation', - { - defaultMessage: - 'Are you sure you want to clear your changes and return to your default results?', - } -); - -export const CONVERT_TO_MANUAL_CONFIRMATION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.convertToManualCurationConfirmation', - { - defaultMessage: 'Are you sure you want to convert this to a manual curation?', - } -); - -export const RESULT_ACTIONS_DIRECTIONS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription', - { defaultMessage: 'Promote results by clicking the star, hide them by clicking the eye.' } -); -export const PROMOTE_DOCUMENT_ACTION = { - title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel', { - defaultMessage: 'Promote this result', - }), - iconType: 'starPlusEmpty', - iconColor: 'primary' as EuiButtonIconProps['color'], -}; -export const DEMOTE_DOCUMENT_ACTION = { - title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel', { - defaultMessage: 'Demote this result', - }), - iconType: 'starMinusFilled', - iconColor: 'primary' as EuiButtonIconProps['color'], -}; -export const HIDE_DOCUMENT_ACTION = { - title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel', { - defaultMessage: 'Hide this result', - }), - iconType: 'eyeClosed', - iconColor: 'danger' as EuiButtonIconProps['color'], -}; -export const SHOW_DOCUMENT_ACTION = { - title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel', { - defaultMessage: 'Show this result', - }), - iconType: 'eye', - iconColor: 'primary' as EuiButtonIconProps['color'], -}; - -export const AUTOMATED_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curation.automatedLabel', - { defaultMessage: 'Automated' } -); - -export const COVERT_TO_MANUAL_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curation.convertToManualCurationButtonLabel', - { defaultMessage: 'Convert to manual curation' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx deleted file mode 100644 index ddc9c69a35c8d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../../__mocks__/react_router'; - -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiBadge, EuiButton, EuiTab } from '@elastic/eui'; - -import { getPageHeaderActions, getPageHeaderTabs, getPageTitle } from '../../../../test_helpers'; - -jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() })); - -import { AppSearchPageTemplate } from '../../layout'; - -import { AutomatedCuration } from './automated_curation'; -import { AutomatedCurationHistory } from './automated_curation_history'; -import { CurationLogic } from './curation_logic'; - -import { DeleteCurationButton } from './delete_curation_button'; -import { PromotedDocuments, OrganicDocuments } from './documents'; - -describe('AutomatedCuration', () => { - const values = { - queries: ['query A', 'query B'], - isFlyoutOpen: false, - curation: { - promoted: [], - hidden: [], - suggestion: { - status: 'applied', - }, - queries: ['foo'], - }, - activeQuery: 'query A', - isAutomated: true, - }; - - const actions = { - convertToManual: jest.fn(), - onSelectPageTab: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - mockUseParams.mockReturnValue({ curationId: 'test' }); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(AppSearchPageTemplate)); - }); - - it('includes set of tabs in the page header', () => { - const wrapper = shallow(); - - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - tabs.at(0).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(1, 'promoted'); - - expect(tabs.at(1).prop('disabled')).toBe(true); - - tabs.at(2).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(2, 'history'); - }); - - it('renders promoted and organic documents when the promoted tab is selected', () => { - setMockValues({ ...values, selectedPageTab: 'promoted' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(0).prop('isSelected')).toEqual(true); - - expect(wrapper.find(PromotedDocuments)).toHaveLength(1); - expect(wrapper.find(OrganicDocuments)).toHaveLength(1); - }); - - it('renders curation history when the history tab is selected', () => { - setMockValues({ ...values, selectedPageTab: 'history' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(2).prop('isSelected')).toEqual(true); - - expect(wrapper.find(AutomatedCurationHistory)).toHaveLength(1); - }); - - it('initializes CurationLogic with a curationId prop from URL param', () => { - mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' }); - shallow(); - - expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' }); - }); - - it('displays the query in the title with a badge', () => { - const wrapper = shallow(); - const pageTitle = shallow(
{getPageTitle(wrapper)}
); - - expect(pageTitle.text()).toContain('query A'); - expect(pageTitle.find(EuiBadge)).toHaveLength(1); - }); - - it('contains a button to delete the curation', () => { - const wrapper = shallow(); - const pageHeaderActions = getPageHeaderActions(wrapper); - - expect(pageHeaderActions.find(DeleteCurationButton)).toHaveLength(1); - }); - - describe('convert to manual button', () => { - let convertToManualButton: ShallowWrapper; - let confirmSpy: jest.SpyInstance; - - beforeAll(() => { - const wrapper = shallow(); - convertToManualButton = getPageHeaderActions(wrapper).find(EuiButton); - - confirmSpy = jest.spyOn(window, 'confirm'); - }); - - afterAll(() => { - confirmSpy.mockRestore(); - }); - - it('converts the curation upon user confirmation', () => { - confirmSpy.mockReturnValueOnce(true); - convertToManualButton.simulate('click'); - - expect(actions.convertToManual).toHaveBeenCalled(); - }); - - it('does not convert the curation if the user cancels', () => { - confirmSpy.mockReturnValueOnce(false); - convertToManualButton.simulate('click'); - - expect(actions.convertToManual).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx deleted file mode 100644 index 0351d4c113d13..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 { useParams } from 'react-router-dom'; - -import { useValues, useActions } from 'kea'; - -import { EuiButton, EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EngineLogic } from '../../engine'; -import { AppSearchPageTemplate } from '../../layout'; -import { AutomatedIcon } from '../components/automated_icon'; - -import { - AUTOMATED_LABEL, - COVERT_TO_MANUAL_BUTTON_LABEL, - CONVERT_TO_MANUAL_CONFIRMATION, -} from '../constants'; - -import { getCurationsBreadcrumbs } from '../utils'; - -import { AutomatedCurationHistory } from './automated_curation_history'; -import { HIDDEN_DOCUMENTS_TITLE, PROMOTED_DOCUMENTS_TITLE } from './constants'; -import { CurationLogic } from './curation_logic'; -import { DeleteCurationButton } from './delete_curation_button'; -import { PromotedDocuments, OrganicDocuments } from './documents'; - -export const AutomatedCuration: React.FC = () => { - const { curationId } = useParams<{ curationId: string }>(); - const logic = CurationLogic({ curationId }); - const { convertToManual, onSelectPageTab } = useActions(logic); - const { activeQuery, queries, curation, selectedPageTab } = useValues(logic); - const { engineName } = useValues(EngineLogic); - - const pageTabs = [ - { - label: PROMOTED_DOCUMENTS_TITLE, - append: {curation.promoted.length}, - isSelected: selectedPageTab === 'promoted', - onClick: () => onSelectPageTab('promoted'), - }, - { - label: HIDDEN_DOCUMENTS_TITLE, - append: 0, - isSelected: false, - disabled: true, - }, - { - label: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyButtonLabel', - { - defaultMessage: 'History', - } - ), - isSelected: selectedPageTab === 'history', - onClick: () => onSelectPageTab('history'), - }, - ]; - - return ( - - {activeQuery}{' '} - - {AUTOMATED_LABEL} - - - ), - rightSideItems: [ - - - - - - { - if (window.confirm(CONVERT_TO_MANUAL_CONFIRMATION)) convertToManual(); - }} - > - {COVERT_TO_MANUAL_BUTTON_LABEL} - - - , - ], - tabs: pageTabs, - }} - > - {selectedPageTab === 'promoted' && } - {selectedPageTab === 'promoted' && } - {selectedPageTab === 'history' && ( - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx deleted file mode 100644 index 7c05a3a6b0f05..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { EuiButtonEmpty } from '@elastic/eui'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { EntSearchLogStream } from '../../../../shared/log_stream'; - -import { DataPanel } from '../../data_panel'; - -import { AutomatedCurationHistory } from './automated_curation_history'; - -describe('AutomatedCurationHistory', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual( - 'appsearch.adaptive_relevance.query: some text and event.kind: event and event.dataset: search-relevance-suggestions and appsearch.adaptive_relevance.engine: foo and event.action: curation_suggestion and appsearch.adaptive_relevance.suggestion.new_status: automated' - ); - }); - - it('sets new endTimestamp when refresh is pressed', async () => { - const wrapper = shallow(); - const initialTimestamp = wrapper.find(EntSearchLogStream).prop('endTimestamp'); - await nextTick(); - - // Find the refresh button and click - shallow(wrapper.find(DataPanel).prop('title')).find(EuiButtonEmpty).simulate('click'); - - wrapper.update(); - - expect(wrapper.find(EntSearchLogStream).prop('endTimestamp')).not.toEqual(initialTimestamp); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx deleted file mode 100644 index 63a7e4cf44a78..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation_history.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EntSearchLogStream } from '../../../../shared/log_stream'; -import { DataPanel } from '../../data_panel'; - -interface Props { - query: string; - engineName: string; -} - -export const AutomatedCurationHistory: React.FC = ({ query, engineName }) => { - const [endTimestamp, setEndTimestamp] = useState(Date.now()); - const filters = [ - `appsearch.adaptive_relevance.query: ${query}`, - 'event.kind: event', - 'event.dataset: search-relevance-suggestions', - `appsearch.adaptive_relevance.engine: ${engineName}`, - 'event.action: curation_suggestion', - 'appsearch.adaptive_relevance.suggestion.new_status: automated', - ]; - - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableTitle', - { defaultMessage: 'Adaptive relevance changes' } - )} - - - setEndTimestamp(Date.now())} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engines.apiLogs.newEventsButtonLabel', - { - defaultMessage: 'Refresh', - } - )} - - - - } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curation.detail.historyTableDescription', - { - defaultMessage: - 'A detailed log of recent changes to curations powered by adaptive relevance.', - } - )} - hasBorder - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/constants.ts deleted file mode 100644 index b5e5108e42e77..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/constants.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const PROMOTED_DOCUMENTS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title', - { defaultMessage: 'Promoted results' } -); - -export const HIDDEN_DOCUMENTS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title', - { defaultMessage: 'Hidden results' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx deleted file mode 100644 index c15089726b6fc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EnterpriseSearchPageTemplateWrapper } from '../../../../shared/layout'; -import { rerender } from '../../../../test_helpers'; - -jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() })); - -import { AutomatedCuration } from './automated_curation'; - -import { ManualCuration } from './manual_curation'; - -import { Curation } from '.'; - -describe('Curation', () => { - const values = { - dataLoading: false, - isAutomated: true, - }; - - const actions = { - loadCuration: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('calls loadCuration on page load & whenever the curationId URL param changes', () => { - mockUseParams.mockReturnValueOnce({ curationId: 'cur-123456789' }); - const wrapper = shallow(); - expect(actions.loadCuration).toHaveBeenCalledTimes(1); - - mockUseParams.mockReturnValueOnce({ curationId: 'cur-987654321' }); - rerender(wrapper); - expect(actions.loadCuration).toHaveBeenCalledTimes(2); - }); - - it('renders a loading view when loading', () => { - setMockValues({ dataLoading: true }); - const wrapper = shallow(); - - expect(wrapper.is(EnterpriseSearchPageTemplateWrapper)).toBe(true); - }); - - it('renders a view for automated curations', () => { - setMockValues({ isAutomated: true }); - const wrapper = shallow(); - - expect(wrapper.is(AutomatedCuration)).toBe(true); - }); - - it('renders a view for manual curations', () => { - setMockValues({ isAutomated: false }); - const wrapper = shallow(); - - expect(wrapper.is(ManualCuration)).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx deleted file mode 100644 index 1c49c077e7a6a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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, { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; - -import { useValues, useActions } from 'kea'; - -import { EnterpriseSearchPageTemplateWrapper } from '../../../../shared/layout'; - -import { AutomatedCuration } from './automated_curation'; -import { CurationLogic } from './curation_logic'; -import { ManualCuration } from './manual_curation'; - -export const Curation: React.FC = () => { - const { curationId } = useParams() as { curationId: string }; - const { loadCuration } = useActions(CurationLogic({ curationId })); - const { dataLoading, isAutomated } = useValues(CurationLogic({ curationId })); - - useEffect(() => { - loadCuration(); - }, [curationId]); - - if (dataLoading) { - return ; - } - return isAutomated ? : ; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts deleted file mode 100644 index 218fc9170b675..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ /dev/null @@ -1,487 +0,0 @@ -/* - * 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, - mockHttpValues, - mockKibanaValues, - mockFlashMessageHelpers, -} from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../../test_helpers'; - -import { CurationLogic } from '.'; - -describe('CurationLogic', () => { - const { mount } = new LogicMounter(CurationLogic); - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; - - const MOCK_CURATION_RESPONSE = { - id: 'cur-123456789', - last_updated: 'some timestamp', - queries: ['some search'], - promoted: [{ id: 'some-promoted-document' }], - organic: [ - { - id: { raw: 'some-organic-document', snippet: null }, - _meta: { id: 'some-organic-document', engine: 'some-engine' }, - }, - ], - hidden: [{ id: 'some-hidden-document' }], - }; - - const DEFAULT_VALUES = { - dataLoading: true, - curation: { - id: '', - last_updated: '', - queries: [], - promoted: [], - organic: [], - hidden: [], - }, - queries: [], - queriesLoading: false, - activeQuery: '', - organicDocumentsLoading: false, - promotedIds: [], - promotedDocumentsLoading: false, - hiddenIds: [], - hiddenDocumentsLoading: false, - isAutomated: false, - selectedPageTab: 'promoted', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(CurationLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onCurationLoad', () => { - it('should set curation, queries, activeQuery, promotedIds, hiddenIds, & all loading states to false', () => { - mount(); - - CurationLogic.actions.onCurationLoad(MOCK_CURATION_RESPONSE); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - curation: MOCK_CURATION_RESPONSE, - queries: ['some search'], - activeQuery: 'some search', - promotedIds: ['some-promoted-document'], - hiddenIds: ['some-hidden-document'], - dataLoading: false, - queriesLoading: false, - organicDocumentsLoading: false, - promotedDocumentsLoading: false, - hiddenDocumentsLoading: false, - }); - }); - - it("should not override activeQuery once it's been set", () => { - mount({ activeQuery: 'test' }); - - CurationLogic.actions.onCurationLoad(MOCK_CURATION_RESPONSE); - - expect(CurationLogic.values.activeQuery).toEqual('test'); - }); - }); - - describe('onCurationError', () => { - it('should set all loading states to false', () => { - mount({ - dataLoading: true, - queriesLoading: true, - organicDocumentsLoading: true, - promotedDocumentsLoading: true, - hiddenDocumentsLoading: true, - }); - - CurationLogic.actions.onCurationError(); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - queriesLoading: false, - organicDocumentsLoading: false, - promotedDocumentsLoading: false, - hiddenDocumentsLoading: false, - }); - }); - }); - - describe('updateQueries', () => { - it('should set queries state & queriesLoading to true', () => { - const values = { ...DEFAULT_VALUES, queries: ['a', 'b'], activeQuery: 'a' }; - mount(values); - - CurationLogic.actions.updateQueries(['a', 'b', 'c']); - - expect(CurationLogic.values).toEqual({ - ...values, - queries: ['a', 'b', 'c'], - queriesLoading: true, - }); - }); - }); - - describe('setActiveQuery', () => { - it('should set activeQuery state & organicDocumentsLoading to true', () => { - mount(); - - CurationLogic.actions.setActiveQuery('some query'); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - activeQuery: 'some query', - organicDocumentsLoading: true, - }); - }); - }); - - describe('setPromotedIds', () => { - it('should set promotedIds state & promotedDocumentsLoading to true', () => { - mount(); - - CurationLogic.actions.setPromotedIds(['hello', 'world']); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - promotedIds: ['hello', 'world'], - promotedDocumentsLoading: true, - }); - }); - }); - - describe('addPromotedId', () => { - it('should set promotedIds state & promotedDocumentsLoading to true', () => { - mount({ promotedIds: ['hello'] }); - - CurationLogic.actions.addPromotedId('world'); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - promotedIds: ['hello', 'world'], - promotedDocumentsLoading: true, - }); - }); - }); - - describe('removePromotedId', () => { - it('should set promotedIds state & promotedDocumentsLoading to true', () => { - mount({ promotedIds: ['hello', 'deleteme', 'world'] }); - - CurationLogic.actions.removePromotedId('deleteme'); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - promotedIds: ['hello', 'world'], - promotedDocumentsLoading: true, - }); - }); - }); - - describe('clearPromotedId', () => { - it('should reset promotedIds state & set promotedDocumentsLoading to true', () => { - mount({ promotedIds: ['hello', 'world'] }); - - CurationLogic.actions.clearPromotedIds(); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - promotedIds: [], - promotedDocumentsLoading: true, - }); - }); - }); - - describe('addHiddenId', () => { - it('should set hiddenIds state & hiddenDocumentsLoading to true', () => { - mount({ hiddenIds: ['hello'] }); - - CurationLogic.actions.addHiddenId('world'); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - hiddenIds: ['hello', 'world'], - hiddenDocumentsLoading: true, - }); - }); - }); - - describe('removeHiddenId', () => { - it('should set hiddenIds state & hiddenDocumentsLoading to true', () => { - mount({ hiddenIds: ['hello', 'deleteme', 'world'] }); - - CurationLogic.actions.removeHiddenId('deleteme'); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - hiddenIds: ['hello', 'world'], - hiddenDocumentsLoading: true, - }); - }); - }); - - describe('clearHiddenId', () => { - it('should reset hiddenIds state & set hiddenDocumentsLoading to true', () => { - mount({ hiddenIds: ['hello', 'world'] }); - - CurationLogic.actions.clearHiddenIds(); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - hiddenIds: [], - hiddenDocumentsLoading: true, - }); - }); - }); - - describe('onSelectPageTab', () => { - it('should set the selected page tab and clears flash messages', () => { - mount({ - selectedPageTab: 'promoted', - }); - - CurationLogic.actions.onSelectPageTab('hidden'); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - selectedPageTab: 'hidden', - }); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - }); - - describe('selectors', () => { - describe('isAutomated', () => { - it('is true when suggestion status is automated', () => { - mount({ curation: { suggestion: { status: 'automated' } } }); - - expect(CurationLogic.values.isAutomated).toBe(true); - }); - - it('is false when suggestion status is not automated', () => { - for (status of ['pending', 'applied', 'rejected', 'disabled']) { - mount({ curation: { suggestion: { status } } }); - - expect(CurationLogic.values.isAutomated).toBe(false); - } - }); - }); - }); - - describe('listeners', () => { - describe('convertToManual', () => { - it('should make an API call and re-load the curation on success', async () => { - http.put.mockReturnValueOnce(Promise.resolve()); - mount({ activeQuery: 'some query' }); - jest.spyOn(CurationLogic.actions, 'loadCuration'); - - CurationLogic.actions.convertToManual(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify([ - { - query: 'some query', - type: 'curation', - status: 'applied', - }, - ]), - } - ); - expect(CurationLogic.actions.loadCuration).toHaveBeenCalled(); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - CurationLogic.actions.convertToManual(); - }); - }); - - describe('deleteCuration', () => { - it('should make an API call and navigate to the curations page', async () => { - http.delete.mockReturnValueOnce(Promise.resolve()); - mount({}, { curationId: 'cur-123456789' }); - jest.spyOn(CurationLogic.actions, 'onCurationLoad'); - - CurationLogic.actions.deleteCuration(); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations/cur-123456789' - ); - expect(flashSuccessToast).toHaveBeenCalled(); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - - itShowsServerErrorAsFlashMessage(http.delete, () => { - mount({}, { curationId: 'cur-404' }); - CurationLogic.actions.deleteCuration(); - }); - }); - - describe('loadCuration', () => { - it('should set dataLoading state', () => { - mount({ dataLoading: false }, { curationId: 'cur-123456789' }); - - CurationLogic.actions.loadCuration(); - - expect(CurationLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and set curation state', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_CURATION_RESPONSE)); - mount({}, { curationId: 'cur-123456789' }); - jest.spyOn(CurationLogic.actions, 'onCurationLoad'); - - CurationLogic.actions.loadCuration(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations/cur-123456789', - { - query: { skip_record_analytics: 'true' }, - } - ); - expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); - }); - - it('handles errors/404s with a redirect to the Curations view', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); - mount({}, { curationId: 'cur-404' }); - - CurationLogic.actions.loadCuration(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error', { isQueued: true }); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - }); - - describe('updateCuration', () => { - beforeAll(() => jest.useFakeTimers({ legacyFakeTimers: true })); - afterAll(() => jest.useRealTimers()); - - it('should make a PUT API call with queries and promoted/hidden IDs to update', async () => { - http.put.mockReturnValueOnce(Promise.resolve(MOCK_CURATION_RESPONSE)); - mount( - { - queries: ['a', 'b', 'c'], - activeQuery: 'b', - promotedIds: ['d', 'e', 'f'], - hiddenIds: ['g'], - }, - { curationId: 'cur-123456789' } - ); - jest.spyOn(CurationLogic.actions, 'onCurationLoad'); - - CurationLogic.actions.updateCuration(); - jest.runAllTimers(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations/cur-123456789', - { - query: { skip_record_analytics: 'true' }, - body: '{"queries":["a","b","c"],"query":"b","promoted":["d","e","f"],"hidden":["g"]}', // Uses state currently in CurationLogic - } - ); - expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); - }); - - it('handles errors', async () => { - http.put.mockReturnValueOnce(Promise.reject('error')); - mount({}, { curationId: 'cur-123456789' }); - jest.spyOn(CurationLogic.actions, 'onCurationError'); - - CurationLogic.actions.updateCuration(); - jest.runAllTimers(); - await nextTick(); - - expect(clearFlashMessages).toHaveBeenCalled(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - expect(CurationLogic.actions.onCurationError).toHaveBeenCalled(); - }); - }); - - describe('listeners that call updateCuration as a side effect', () => { - beforeAll(() => { - mount(); - jest.spyOn(CurationLogic.actions, 'updateCuration').mockImplementation(() => {}); - }); - - afterAll(() => { - (CurationLogic.actions.updateCuration as jest.Mock).mockRestore(); - }); - - afterEach(() => { - expect(CurationLogic.actions.updateCuration).toHaveBeenCalled(); - }); - - describe('updateQueries', () => { - it('calls updateCuration', () => { - CurationLogic.actions.updateQueries(['hello', 'world']); - }); - - it('should also call setActiveQuery if the current activeQuery was deleted from queries', () => { - jest.spyOn(CurationLogic.actions, 'setActiveQuery'); - CurationLogic.actions.updateQueries(['world']); - expect(CurationLogic.actions.setActiveQuery).toHaveBeenCalledWith('world'); - }); - }); - - it('setActiveQuery', () => { - CurationLogic.actions.setActiveQuery('test'); - }); - - it('setPromotedIds', () => { - CurationLogic.actions.setPromotedIds(['test']); - }); - - it('addPromotedId', () => { - CurationLogic.actions.addPromotedId('test'); - }); - - it('removePromotedId', () => { - CurationLogic.actions.removePromotedId('test'); - }); - - it('clearPromotedIds', () => { - CurationLogic.actions.clearPromotedIds(); - }); - - it('addHiddenId', () => { - CurationLogic.actions.addHiddenId('test'); - }); - - it('removeHiddenId', () => { - CurationLogic.actions.removeHiddenId('test'); - }); - - it('clearHiddenIds', () => { - CurationLogic.actions.clearHiddenIds(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts deleted file mode 100644 index bd5333d5fa541..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts +++ /dev/null @@ -1,305 +0,0 @@ -/* - * 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 { - clearFlashMessages, - flashAPIErrors, - flashSuccessToast, -} from '../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../shared/http'; -import { KibanaLogic } from '../../../../shared/kibana'; -import { ENGINE_CURATIONS_PATH } from '../../../routes'; -import { EngineLogic, generateEnginePath } from '../../engine'; -import { DELETE_SUCCESS_MESSAGE } from '../constants'; - -import { Curation } from '../types'; -import { addDocument, removeDocument } from '../utils'; - -type CurationPageTabs = 'promoted' | 'history' | 'hidden'; - -interface CurationValues { - dataLoading: boolean; - curation: Curation; - queries: Curation['queries']; - queriesLoading: boolean; - activeQuery: string; - organicDocumentsLoading: boolean; - promotedIds: string[]; - promotedDocumentsLoading: boolean; - hiddenIds: string[]; - hiddenDocumentsLoading: boolean; - isAutomated: boolean; - selectedPageTab: CurationPageTabs; -} - -interface CurationActions { - convertToManual(): void; - deleteCuration(): void; - loadCuration(): void; - onCurationLoad(curation: Curation): { curation: Curation }; - updateCuration(): void; - onCurationError(): void; - updateQueries(queries: Curation['queries']): { queries: Curation['queries'] }; - setActiveQuery(query: string): { query: string }; - setPromotedIds(promotedIds: string[]): { promotedIds: string[] }; - addPromotedId(id: string): { id: string }; - removePromotedId(id: string): { id: string }; - clearPromotedIds(): void; - addHiddenId(id: string): { id: string }; - removeHiddenId(id: string): { id: string }; - clearHiddenIds(): void; - onSelectPageTab(pageTab: CurationPageTabs): { pageTab: CurationPageTabs }; -} - -interface CurationProps { - curationId: Curation['id']; -} - -export const CurationLogic = kea>({ - path: ['enterprise_search', 'app_search', 'curation_logic'], - actions: () => ({ - convertToManual: true, - deleteCuration: true, - loadCuration: true, - onCurationLoad: (curation) => ({ curation }), - updateCuration: true, - onCurationError: true, - updateQueries: (queries) => ({ queries }), - setActiveQuery: (query) => ({ query }), - setPromotedIds: (promotedIds) => ({ promotedIds }), - addPromotedId: (id) => ({ id }), - removePromotedId: (id) => ({ id }), - clearPromotedIds: true, - addHiddenId: (id) => ({ id }), - removeHiddenId: (id) => ({ id }), - clearHiddenIds: true, - onSelectPageTab: (pageTab) => ({ pageTab }), - }), - reducers: () => ({ - dataLoading: [ - true, - { - loadCuration: () => true, - onCurationLoad: () => false, - onCurationError: () => false, - }, - ], - curation: [ - { - id: '', - last_updated: '', - queries: [], - promoted: [], - organic: [], - hidden: [], - }, - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationLoad: (_, { curation }) => curation, - }, - ], - queries: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationLoad: (_, { curation }) => curation.queries, - // @ts-expect-error upgrade typescript v5.1.6 - updateQueries: (_, { queries }) => queries, - }, - ], - queriesLoading: [ - false, - { - updateQueries: () => true, - onCurationLoad: () => false, - onCurationError: () => false, - }, - ], - activeQuery: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setActiveQuery: (_, { query }) => query, - // @ts-expect-error upgrade typescript v5.1.6 - onCurationLoad: (activeQuery, { curation }) => activeQuery || curation.queries[0], - }, - ], - organicDocumentsLoading: [ - false, - { - setActiveQuery: () => true, - onCurationLoad: () => false, - onCurationError: () => false, - }, - ], - promotedIds: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationLoad: (_, { curation }) => curation.promoted.map((document) => document.id), - // @ts-expect-error upgrade typescript v5.1.6 - setPromotedIds: (_, { promotedIds }) => promotedIds, - // @ts-expect-error upgrade typescript v5.1.6 - addPromotedId: (promotedIds, { id }) => addDocument(promotedIds, id), - // @ts-expect-error upgrade typescript v5.1.6 - removePromotedId: (promotedIds, { id }) => removeDocument(promotedIds, id), - clearPromotedIds: () => [], - }, - ], - promotedDocumentsLoading: [ - false, - { - setPromotedIds: () => true, - addPromotedId: () => true, - removePromotedId: () => true, - clearPromotedIds: () => true, - onCurationLoad: () => false, - onCurationError: () => false, - }, - ], - hiddenIds: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationLoad: (_, { curation }) => curation.hidden.map((document) => document.id), - // @ts-expect-error upgrade typescript v5.1.6 - addHiddenId: (hiddenIds, { id }) => addDocument(hiddenIds, id), - // @ts-expect-error upgrade typescript v5.1.6 - removeHiddenId: (hiddenIds, { id }) => removeDocument(hiddenIds, id), - clearHiddenIds: () => [], - }, - ], - hiddenDocumentsLoading: [ - false, - { - addHiddenId: () => true, - removeHiddenId: () => true, - clearHiddenIds: () => true, - onCurationLoad: () => false, - onCurationError: () => false, - }, - ], - selectedPageTab: [ - 'promoted', - { - // @ts-expect-error upgrade typescript v5.1.6 - onSelectPageTab: (_, { pageTab }) => pageTab, - }, - ], - }), - selectors: ({ selectors }) => ({ - isAutomated: [ - () => [selectors.curation], - (curation: CurationValues['curation']) => { - return curation.suggestion?.status === 'automated'; - }, - ], - }), - listeners: ({ actions, values, props }) => ({ - convertToManual: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - await http.put( - `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, - { - body: JSON.stringify([ - { - query: values.activeQuery, - type: 'curation', - status: 'applied', - }, - ]), - } - ); - actions.loadCuration(); - } catch (e) { - flashAPIErrors(e); - } - }, - deleteCuration: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { navigateToUrl } = KibanaLogic.values; - - try { - await http.delete( - `/internal/app_search/engines/${engineName}/curations/${props.curationId}` - ); - navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - flashSuccessToast(DELETE_SUCCESS_MESSAGE); - } catch (e) { - flashAPIErrors(e); - } - }, - loadCuration: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/curations/${props.curationId}`, - { query: { skip_record_analytics: 'true' } } - ); - - actions.onCurationLoad(response); - } catch (e) { - const { navigateToUrl } = KibanaLogic.values; - - flashAPIErrors(e, { isQueued: true }); - navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - } - }, - updateCuration: async (_, breakpoint) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - await breakpoint(100); - clearFlashMessages(); - - try { - const response = await http.put( - `/internal/app_search/engines/${engineName}/curations/${props.curationId}`, - { - query: { skip_record_analytics: 'true' }, - body: JSON.stringify({ - queries: values.queries, - query: values.activeQuery, - promoted: values.promotedIds, - hidden: values.hiddenIds, - }), - } - ); - - actions.onCurationLoad(response); - } catch (e) { - flashAPIErrors(e); - actions.onCurationError(); - } - }, - updateQueries: ({ queries }) => { - const activeQueryDeleted = !queries.includes(values.activeQuery); - if (activeQueryDeleted) actions.setActiveQuery(queries[0]); - - actions.updateCuration(); - }, - onSelectPageTab: () => { - clearFlashMessages(); - }, - setActiveQuery: () => actions.updateCuration(), - setPromotedIds: () => actions.updateCuration(), - addPromotedId: () => actions.updateCuration(), - removePromotedId: () => actions.updateCuration(), - clearPromotedIds: () => actions.updateCuration(), - addHiddenId: () => actions.updateCuration(), - removeHiddenId: () => actions.updateCuration(), - clearHiddenIds: () => actions.updateCuration(), - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/delete_curation_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/delete_curation_button.test.tsx deleted file mode 100644 index 2a69b2bcd9748..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/delete_curation_button.test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions } from '../../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() })); - -import { DeleteCurationButton } from './delete_curation_button'; - -describe('DeleteCurationButton', () => { - const actions = { - deleteCuration: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' }); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(EuiButton)).toBe(true); - }); - - describe('restore defaults button', () => { - let wrapper: ShallowWrapper; - let confirmSpy: jest.SpyInstance; - - beforeAll(() => { - wrapper = shallow(); - confirmSpy = jest.spyOn(window, 'confirm'); - }); - - afterAll(() => { - confirmSpy.mockRestore(); - }); - - it('resets the curation upon user confirmation', () => { - confirmSpy.mockReturnValueOnce(true); - wrapper.simulate('click'); - expect(actions.deleteCuration).toHaveBeenCalled(); - }); - - it('does not reset the curation if the user cancels', () => { - confirmSpy.mockReturnValueOnce(false); - wrapper.simulate('click'); - expect(actions.deleteCuration).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/delete_curation_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/delete_curation_button.tsx deleted file mode 100644 index 49e1dc8b70b19..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/delete_curation_button.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { useParams } from 'react-router-dom'; - -import { useActions } from 'kea'; - -import { EuiButton } from '@elastic/eui'; - -import { DELETE_BUTTON_LABEL } from '../../../../shared/constants'; -import { DELETE_CONFIRMATION_MESSAGE } from '../constants'; - -import { CurationLogic } from '.'; - -export const DeleteCurationButton: React.FC = () => { - const { curationId } = useParams() as { curationId: string }; - const { deleteCuration } = useActions(CurationLogic({ curationId })); - - return ( - { - if (window.confirm(DELETE_CONFIRMATION_MESSAGE)) deleteCuration(); - }} - > - {DELETE_BUTTON_LABEL} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx deleted file mode 100644 index 823189c006c8d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt, EuiButtonEmpty, EuiBadge } from '@elastic/eui'; - -import { DataPanel } from '../../../data_panel'; -import { CurationResult } from '../results'; - -import { HiddenDocuments } from '.'; - -describe('HiddenDocuments', () => { - const values = { - curation: { - hidden: [ - { id: 'mock-document-1' }, - { id: 'mock-document-2' }, - { id: 'mock-document-3' }, - { id: 'mock-document-4' }, - { id: 'mock-document-5' }, - ], - }, - hiddenDocumentsLoading: false, - }; - const actions = { - removeHiddenId: jest.fn(), - clearHiddenIds: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a list of hidden documents', () => { - const wrapper = shallow(); - - expect(wrapper.find(CurationResult)).toHaveLength(5); - }); - - it('displays the number of documents in a badge', () => { - const wrapper = shallow(); - const Icon = wrapper.prop('iconType'); - const iconWrapper = shallow(); - - expect(iconWrapper.find(EuiBadge).prop('children')).toEqual(5); - }); - - it('renders an empty state & hides the panel actions when empty', () => { - setMockValues({ ...values, curation: { hidden: [] } }); - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - expect(wrapper.find(DataPanel).prop('action')).toBe(false); - }); - - it('renders a loading state', () => { - setMockValues({ ...values, hiddenDocumentsLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true); - }); - - describe('actions', () => { - it('renders results with an action button that un-hides the result', () => { - const wrapper = shallow(); - const result = wrapper.find(CurationResult).last(); - result.prop('actions')[0].onClick(); - - expect(actions.removeHiddenId).toHaveBeenCalledWith('mock-document-5'); - }); - - it('renders a restore all button that un-hides all hidden results', () => { - const wrapper = shallow(); - const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); - - panelActions.find(EuiButtonEmpty).simulate('click'); - expect(actions.clearHiddenIds).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx deleted file mode 100644 index 5e6123efa988e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/hidden_documents.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiBadge, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DataPanel } from '../../../data_panel'; - -import { SHOW_DOCUMENT_ACTION } from '../../constants'; -import { HIDDEN_DOCUMENTS_TITLE } from '../constants'; -import { CurationLogic } from '../curation_logic'; -import { AddResultButton, CurationResult, convertToResultFormat } from '../results'; - -export const HiddenDocuments: React.FC = () => { - const { clearHiddenIds, removeHiddenId } = useActions(CurationLogic); - const { curation, hiddenDocumentsLoading } = useValues(CurationLogic); - - const documents = curation.hidden; - const hasDocuments = documents.length > 0; - - const CountBadge: React.FC = () => {documents.length}; - - return ( - {HIDDEN_DOCUMENTS_TITLE}} - action={ - hasDocuments && ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel', - { defaultMessage: 'Unhide all' } - )} - - - - - - - ) - } - isLoading={hiddenDocumentsLoading} - > - {hasDocuments ? ( - - {documents.map((document, index) => ( - - removeHiddenId(document.id), - }, - ]} - /> - - ))} - - ) : ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle', - { defaultMessage: "You haven't hidden any documents yet" } - )} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription', - { - defaultMessage: - 'Hide documents by clicking the eye icon on the organic results above, or search and hide a result manually.', - } - )} - actions={} - /> - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts deleted file mode 100644 index 3548f6f298069..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { PromotedDocuments } from './promoted_documents'; -export { OrganicDocuments } from './organic_documents'; -export { HiddenDocuments } from './hidden_documents'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx deleted file mode 100644 index 841dffbe3642e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiSkeletonText, EuiEmptyPrompt } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { DataPanel } from '../../../data_panel'; -import { CurationResult } from '../results'; - -import { OrganicDocuments } from '.'; - -describe('OrganicDocuments', () => { - const values = { - curation: { - queries: ['hello', 'world'], - organic: [ - { id: { raw: 'mock-document-1' } }, - { id: { raw: 'mock-document-2' } }, - { id: { raw: 'mock-document-3' } }, - ], - }, - activeQuery: 'world', - organicDocumentsLoading: false, - isAutomated: false, - }; - const actions = { - addPromotedId: jest.fn(), - addHiddenId: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a list of organic results', () => { - const wrapper = shallow(); - - expect(wrapper.find(CurationResult)).toHaveLength(3); - }); - - it('renders the currently active query in the title', () => { - setMockValues({ ...values, activeQuery: 'world' }); - const wrapper = shallow(); - const titleText = shallow(wrapper.find(DataPanel).prop('title')).text(); - - expect(titleText).toEqual('Top organic documents for "world"'); - }); - - it('renders a loading state', () => { - setMockValues({ ...values, organicDocumentsLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSkeletonText).prop('isLoading')).toEqual(true); - }); - - describe('empty state', () => { - it('renders when organic results is empty', () => { - setMockValues({ ...values, curation: { organic: [] } }); - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); - - it('renders when organic results is undefined', () => { - setMockValues({ ...values, curation: { organic: undefined } }); - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); - - it('tells the user to modify the query if the curation is manual', () => { - setMockValues({ ...values, curation: { organic: [] }, isAutomated: false }); - const wrapper = shallow(); - const emptyPromptBody = mountWithIntl(<>{wrapper.find(EuiEmptyPrompt).prop('body')}); - - expect(emptyPromptBody.text()).toContain('Add or change'); - }); - }); - - describe('actions', () => { - it('renders results with an action button that promotes the result', () => { - const wrapper = shallow(); - const result = wrapper.find(CurationResult).first(); - result.prop('actions')[1].onClick(); - - expect(actions.addPromotedId).toHaveBeenCalledWith('mock-document-1'); - }); - - it('renders results with an action button that hides the result', () => { - const wrapper = shallow(); - const result = wrapper.find(CurationResult).last(); - result.prop('actions')[0].onClick(); - - expect(actions.addHiddenId).toHaveBeenCalledWith('mock-document-3'); - }); - - it('hides actions when the curation is automated', () => { - setMockValues({ ...values, isAutomated: true }); - const wrapper = shallow(); - const result = wrapper.find(CurationResult).first(); - - expect(result.prop('actions')).toEqual([]); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx deleted file mode 100644 index 418276fd31f85..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiSkeletonText, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { SearchResult } from '@elastic/search-ui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { LeafIcon } from '../../../../../shared/icons'; - -import { DataPanel } from '../../../data_panel'; - -import { PROMOTE_DOCUMENT_ACTION, HIDE_DOCUMENT_ACTION } from '../../constants'; -import { CurationLogic } from '../curation_logic'; -import { CurationResult } from '../results'; - -export const OrganicDocuments: React.FC = () => { - const { addPromotedId, addHiddenId } = useActions(CurationLogic); - const { curation, activeQuery, isAutomated, organicDocumentsLoading } = useValues(CurationLogic); - - const documents = curation.organic || []; - const hasDocuments = documents.length > 0 && !organicDocumentsLoading; - const currentQuery = activeQuery; - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.title', - { - defaultMessage: 'Top organic documents for "{currentQuery}"', - values: { currentQuery }, - } - )} - - } - > - - {hasDocuments ? ( - - {documents.map((document: SearchResult, index) => ( - - addHiddenId(document.id.raw), - }, - { - ...PROMOTE_DOCUMENT_ACTION, - onClick: () => addPromotedId(document.id.raw), - }, - ] - } - /> - - ))} - - ) : ( - - {' '} - - - ), - }} - /> - } - /> - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.scss deleted file mode 100644 index 59346b66af607..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.scss +++ /dev/null @@ -1,7 +0,0 @@ -.promotedDocuments { - &--results { - border-radius: $euiSizeM; - border: $euiBorderWidthThick solid $euiColorPrimary; - background-color: tintOrShade($euiColorPrimary, 90%, 70%); // Copied from @elastit/eui/src/global_styling/variables/_panels.scss - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx deleted file mode 100644 index 0df3e20c52fbd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { - EuiDragDropContext, - EuiDraggable, - EuiEmptyPrompt, - EuiButtonEmpty, - EuiBadge, - EuiTextColor, -} from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; -import { DataPanel } from '../../../data_panel'; -import { CurationResult } from '../results'; - -import { PromotedDocuments } from '.'; - -describe('PromotedDocuments', () => { - const values = { - curation: { - promoted: [ - { id: 'mock-document-1' }, - { id: 'mock-document-2' }, - { id: 'mock-document-3' }, - { id: 'mock-document-4' }, - ], - }, - promotedIds: ['mock-document-1', 'mock-document-2', 'mock-document-3', 'mock-document-4'], - promotedDocumentsLoading: false, - }; - const actions = { - setPromotedIds: jest.fn(), - clearPromotedIds: jest.fn(), - removePromotedId: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - const getDraggableChildren = (draggableWrapper: any) => { - return draggableWrapper.renderProp('children')({}, {}, {}); - }; - - it('displays the number of documents in a badge', () => { - const wrapper = shallow(); - const Icon = wrapper.prop('iconType'); - const iconWrapper = shallow(); - - expect(iconWrapper.find(EuiBadge).prop('children')).toEqual(4); - }); - - it('renders a list of draggable promoted documents', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiDraggable)).toHaveLength(4); - - wrapper.find(EuiDraggable).forEach((draggableWrapper) => { - expect(getDraggableChildren(draggableWrapper).find(CurationResult).exists()).toBe(true); - }); - }); - - describe('empty state', () => { - it('renders', () => { - setMockValues({ ...values, curation: { promoted: [] } }); - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); - - it('hide information about starring documents if the curation is automated', () => { - setMockValues({ ...values, curation: { promoted: [] }, isAutomated: true }); - const wrapper = shallow(); - const emptyPromptBody = mountWithIntl(<>{wrapper.find(EuiEmptyPrompt).prop('body')}); - - expect(emptyPromptBody.text()).not.toContain('Star documents'); - }); - }); - - it('shows a message when the curation is automated', () => { - setMockValues({ ...values, isAutomated: true }); - const wrapper = shallow(); - const panelAction = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); - - expect(panelAction.find(EuiTextColor)).toHaveLength(1); - }); - - it('renders a loading state', () => { - setMockValues({ ...values, promotedDocumentsLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true); - }); - - describe('actions', () => { - it('renders results with an action button that demotes the result', () => { - const wrapper = shallow(); - const result = getDraggableChildren(wrapper.find(EuiDraggable).last()); - result.prop('actions')[0].onClick(); - - expect(actions.removePromotedId).toHaveBeenCalledWith('mock-document-4'); - }); - - it('hides demote button for results when the curation is automated', () => { - setMockValues({ ...values, isAutomated: true }); - const wrapper = shallow(); - const result = getDraggableChildren(wrapper.find(EuiDraggable).last()); - - expect(result.prop('actions')).toEqual([]); - }); - - it('renders a demote all button that demotes all hidden results', () => { - const wrapper = shallow(); - const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement); - - panelActions.find(EuiButtonEmpty).simulate('click'); - expect(actions.clearPromotedIds).toHaveBeenCalled(); - }); - - it('hides the demote all button when there are on promoted results', () => { - setMockValues({ ...values, curation: { promoted: [] } }); - const wrapper = shallow(); - - expect(wrapper.find(DataPanel).prop('action')).toEqual(false); - }); - - describe('dragging', () => { - it('calls setPromotedIds with the reordered list when users are done dragging', () => { - const wrapper = shallow(); - wrapper.find(EuiDragDropContext).simulate('dragEnd', { - source: { index: 3 }, - destination: { index: 0 }, - }); - - expect(actions.setPromotedIds).toHaveBeenCalledWith([ - 'mock-document-4', - 'mock-document-1', - 'mock-document-2', - 'mock-document-3', - ]); - }); - - it('does not error if source/destination are unavailable on drag end', () => { - const wrapper = shallow(); - wrapper.find(EuiDragDropContext).simulate('dragEnd', {}); - - expect(actions.setPromotedIds).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx deleted file mode 100644 index ab5bf2876ef0b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiEmptyPrompt, - EuiButtonEmpty, - EuiDragDropContext, - DropResult, - EuiDroppable, - EuiDraggable, - euiDragDropReorder, - EuiBadge, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DataPanel } from '../../../data_panel'; - -import { DEMOTE_DOCUMENT_ACTION } from '../../constants'; -import { PROMOTED_DOCUMENTS_TITLE } from '../constants'; -import { CurationLogic } from '../curation_logic'; -import { AddResultButton, CurationResult, convertToResultFormat } from '../results'; - -import './promoted_documents.scss'; - -export const PromotedDocuments: React.FC = () => { - const { curation, isAutomated, promotedIds, promotedDocumentsLoading } = useValues(CurationLogic); - const documents = curation.promoted; - const hasDocuments = documents.length > 0; - - const { setPromotedIds, clearPromotedIds, removePromotedId } = useActions(CurationLogic); - const reorderPromotedIds = ({ source, destination }: DropResult) => { - if (source && destination) { - const reorderedIds = euiDragDropReorder(promotedIds, source.index, destination.index); - setPromotedIds(reorderedIds); - } - }; - - const CountBadge: React.FC = () => {documents.length}; - - return ( - {PROMOTED_DOCUMENTS_TITLE}} - action={ - isAutomated ? ( - -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.managedByAppSearchDescription', - { defaultMessage: 'This curation is being automated by App Search' } - )} - -

-
- ) : ( - hasDocuments && ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel', - { defaultMessage: 'Demote all' } - )} - - - - - - - ) - ) - } - isLoading={promotedDocumentsLoading} - > - {hasDocuments ? ( - - - - {documents.map((document, index) => ( - - - {(provided) => ( - removePromotedId(document.id), - }, - ] - } - dragHandleProps={provided.dragHandleProps} - /> - )} - - - ))} - - - - ) : ( - } - /> - )} -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/index.ts deleted file mode 100644 index 7e458959b2aa1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { CurationLogic } from './curation_logic'; -export { Curation } from './curation'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.test.tsx deleted file mode 100644 index 548d111d6f96e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.test.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiTab } from '@elastic/eui'; - -import { getPageTitle, getPageHeaderActions, getPageHeaderTabs } from '../../../../test_helpers'; - -jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() })); -import { CurationLogic } from './curation_logic'; - -import { DeleteCurationButton } from './delete_curation_button'; -import { PromotedDocuments, HiddenDocuments, OrganicDocuments } from './documents'; -import { ManualCuration } from './manual_curation'; -import { ActiveQuerySelect, ManageQueriesModal } from './queries'; -import { AddResultFlyout } from './results'; -import { SuggestedDocumentsCallout } from './suggested_documents_callout'; - -describe('ManualCuration', () => { - const values = { - queries: ['query A', 'query B'], - isFlyoutOpen: false, - selectedPageTab: 'promoted', - curation: { - promoted: [], - hidden: [], - }, - }; - const actions = { - deleteCuration: jest.fn(), - onSelectPageTab: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a view for managing a curation', () => { - const wrapper = shallow(); - - expect(getPageTitle(wrapper)).toEqual('Manage curation'); - expect(wrapper.prop('pageChrome')).toEqual([ - 'Engines', - 'some-engine', - 'Curations', - 'query A, query B', - ]); - }); - - it('includes set of tabs in the page header', () => { - const wrapper = shallow(); - - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - tabs.at(0).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(1, 'promoted'); - - tabs.at(1).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(2, 'hidden'); - }); - - it('contains a suggested documents callout', () => { - const wrapper = shallow(); - - expect(wrapper.find(SuggestedDocumentsCallout)).toHaveLength(1); - }); - - it('renders promoted and organic documents when the promoted tab is selected', () => { - setMockValues({ ...values, selectedPageTab: 'promoted' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(0).prop('isSelected')).toEqual(true); - - expect(wrapper.find(PromotedDocuments)).toHaveLength(1); - expect(wrapper.find(OrganicDocuments)).toHaveLength(1); - }); - - it('renders hidden documents when the hidden tab is selected', () => { - setMockValues({ ...values, selectedPageTab: 'hidden' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(1).prop('isSelected')).toEqual(true); - - expect(wrapper.find(HiddenDocuments)).toHaveLength(1); - }); - - it('renders the add result flyout when open', () => { - setMockValues({ ...values, isFlyoutOpen: true }); - const wrapper = shallow(); - - expect(wrapper.find(AddResultFlyout)).toHaveLength(1); - }); - - it('initializes CurationLogic with a curationId prop from URL param', () => { - mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' }); - shallow(); - - expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' }); - }); - - describe('page header actions', () => { - let pageHeaderActions: ShallowWrapper; - - beforeAll(() => { - const wrapper = shallow(); - pageHeaderActions = getPageHeaderActions(wrapper); - }); - - it('contains a button to manage queries and an active query selector', () => { - expect(pageHeaderActions.find(ManageQueriesModal)).toHaveLength(1); - }); - - it('contains a button to delete the curation', () => { - expect(pageHeaderActions.find(DeleteCurationButton)).toHaveLength(1); - }); - }); - - it('contains an active query selector', () => { - const wrapper = shallow(); - - expect(wrapper.find(ActiveQuerySelect)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.tsx deleted file mode 100644 index 45b1b6212f504..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 { useParams } from 'react-router-dom'; - -import { useValues, useActions } from 'kea'; - -import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { AppSearchPageTemplate } from '../../layout'; -import { MANAGE_CURATION_TITLE } from '../constants'; -import { getCurationsBreadcrumbs } from '../utils'; - -import { PROMOTED_DOCUMENTS_TITLE, HIDDEN_DOCUMENTS_TITLE } from './constants'; -import { CurationLogic } from './curation_logic'; -import { DeleteCurationButton } from './delete_curation_button'; -import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents'; -import { ActiveQuerySelect, ManageQueriesModal } from './queries'; -import { AddResultLogic, AddResultFlyout } from './results'; -import { SuggestedDocumentsCallout } from './suggested_documents_callout'; - -export const ManualCuration: React.FC = () => { - const { curationId } = useParams() as { curationId: string }; - const logic = CurationLogic({ curationId }); - const { onSelectPageTab } = useActions(logic); - const { queries, selectedPageTab, curation } = useValues(logic); - - const { isFlyoutOpen } = useValues(AddResultLogic); - - const pageTabs = [ - { - label: PROMOTED_DOCUMENTS_TITLE, - append: {curation.promoted.length}, - isSelected: selectedPageTab === 'promoted', - onClick: () => onSelectPageTab('promoted'), - }, - { - label: HIDDEN_DOCUMENTS_TITLE, - append: {curation.hidden.length}, - isSelected: selectedPageTab === 'hidden', - onClick: () => onSelectPageTab('hidden'), - }, - ]; - - return ( - - - - - - - - , - ], - tabs: pageTabs, - }} - > - - {selectedPageTab === 'promoted' && } - {selectedPageTab === 'promoted' && } - {selectedPageTab === 'hidden' && } - - - {isFlyoutOpen && } - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/active_query_select.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/active_query_select.test.tsx deleted file mode 100644 index 5b3ef09ffd2ca..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/active_query_select.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiSelect } from '@elastic/eui'; - -import { ActiveQuerySelect } from '.'; - -describe('ActiveQuerySelect', () => { - const values = { - queries: ['hello', 'world'], - activeQuery: 'world', - queriesLoading: false, - }; - - const actions = { - setActiveQuery: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders select options that correspond to activeQuery & queries', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiSelect).prop('options')).toHaveLength(2); - expect(wrapper.find(EuiSelect).prop('value')).toEqual('world'); - }); - - it('renders a loading state based on queriesLoading', () => { - setMockValues({ ...values, queriesLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSelect).prop('isLoading')).toEqual(true); - }); - - it('calls setActiveQuery on select change', () => { - const wrapper = shallow(); - wrapper.find(EuiSelect).simulate('change', { target: { value: 'new active query' } }); - - expect(actions.setActiveQuery).toHaveBeenCalledWith('new active query'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/active_query_select.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/active_query_select.tsx deleted file mode 100644 index 4e62e3fb7ec7a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/active_query_select.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiFormRow, EuiSelect } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CurationLogic } from '../curation_logic'; - -export const ActiveQuerySelect: React.FC = () => { - const { setActiveQuery } = useActions(CurationLogic); - const { queries, activeQuery, queriesLoading } = useValues(CurationLogic); - - return ( - - ({ - value: query, - text: query, - }))} - value={activeQuery} - onChange={(e) => setActiveQuery(e.target.value)} - isLoading={queriesLoading} - fullWidth - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/index.ts deleted file mode 100644 index 9d7dc9d16c61d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { ActiveQuerySelect } from './active_query_select'; -export { ManageQueriesModal } from './manage_queries_modal'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/manage_queries_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/manage_queries_modal.test.tsx deleted file mode 100644 index a305ab53b4258..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/manage_queries_modal.test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton, EuiModal } from '@elastic/eui'; - -import { MultiInputRows } from '../../../multi_input_rows'; - -import { ManageQueriesModal } from '.'; - -describe('ManageQueriesModal', () => { - const values = { - queries: ['hello', 'world'], - queriesLoading: false, - }; - - const actions = { - updateQueries: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - describe('modal button', () => { - it('renders a modal toggle button', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiButton)).toHaveLength(1); - expect(wrapper.find(EuiButton).prop('onClick')).toBeTruthy(); - }); - - it('renders the toggle button with a loading state when queriesLoading is true', () => { - setMockValues({ ...values, queriesLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiButton).prop('isLoading')).toBe(true); - }); - }); - - describe('modal', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - wrapper = shallow(); - wrapper.find(EuiButton).simulate('click'); - }); - - it('renders the modal when the toggle button has been clicked', () => { - expect(wrapper.find(EuiModal)).toHaveLength(1); - }); - - it('closes the modal', () => { - wrapper.find(EuiModal).simulate('close'); - expect(wrapper.find(EuiModal)).toHaveLength(0); - }); - - it('renders the MultiInputRows component with curation queries', () => { - expect(wrapper.find(MultiInputRows)).toHaveLength(1); - expect(wrapper.find(MultiInputRows).prop('initialValues')).toEqual(['hello', 'world']); - }); - - it('calls updateCuration and closes the modal on MultiInputRows form submit', () => { - wrapper.find(MultiInputRows).simulate('submit', ['new', 'queries']); - - expect(actions.updateQueries).toHaveBeenCalledWith(['new', 'queries']); - expect(wrapper.find(EuiModal)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/manage_queries_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/manage_queries_modal.tsx deleted file mode 100644 index 0ccff49503c70..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/queries/manage_queries_modal.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiText, - EuiSpacer, - EuiButton, - useGeneratedHtmlId, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; -import { MultiInputRows } from '../../../multi_input_rows'; - -import { QUERY_INPUTS_BUTTON, QUERY_INPUTS_PLACEHOLDER } from '../../constants'; -import { CurationLogic } from '../curation_logic'; - -export const ManageQueriesModal: React.FC = () => { - const { queries, queriesLoading } = useValues(CurationLogic); - const { updateQueries } = useActions(CurationLogic); - - const [isModalVisible, setModalVisibility] = useState(false); - const showModal = () => setModalVisibility(true); - const hideModal = () => setModalVisibility(false); - const modalTitleId = useGeneratedHtmlId(); - - return ( - <> - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel', - { defaultMessage: 'Manage queries' } - )} - - {isModalVisible && ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.manageQueryTitle', - { defaultMessage: 'Manage queries' } - )} - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.manageQueryDescription', - { defaultMessage: 'Edit, add, or remove queries for this curation.' } - )} -

-
- - { - updateQueries(newQueries); - hideModal(); - }} - /> -
-
- )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx deleted file mode 100644 index 9a180ff64774b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { AddResultButton } from '.'; - -describe('AddResultButton', () => { - const values = { - isAutomated: false, - }; - - const actions = { - openFlyout: jest.fn(), - }; - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(EuiButton)).toBe(true); - }); - - it('opens the add result flyout on click', () => { - setMockActions(actions); - const wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - expect(actions.openFlyout).toHaveBeenCalled(); - }); - - it('is disbled when the curation is automated', () => { - setMockValues({ ...values, isAutomated: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiButton).prop('disabled')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx deleted file mode 100644 index 912764d4db255..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CurationLogic } from '..'; - -import { AddResultLogic } from '.'; - -export const AddResultButton: React.FC = () => { - const { openFlyout } = useActions(AddResultLogic); - const { isAutomated } = useValues(CurationLogic); - - return ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', { - defaultMessage: 'Add result manually', - })} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.test.tsx deleted file mode 100644 index e544b6f7dccf8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.test.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyout, EuiFieldSearch, EuiEmptyPrompt } from '@elastic/eui'; - -import { CurationResult, AddResultFlyout } from '.'; - -describe('AddResultFlyout', () => { - const values = { - searchDataLoading: false, - searchQuery: '', - searchResults: [], - promotedIds: [], - hiddenIds: [], - }; - const actions = { - search: jest.fn(), - closeFlyout: jest.fn(), - addPromotedId: jest.fn(), - removePromotedId: jest.fn(), - addHiddenId: jest.fn(), - removeHiddenId: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a closeable flyout', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFlyout)).toHaveLength(1); - - wrapper.find(EuiFlyout).simulate('close'); - expect(actions.closeFlyout).toHaveBeenCalled(); - }); - - describe('search input', () => { - it('renders isLoading state correctly', () => { - setMockValues({ ...values, searchDataLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldSearch).prop('isLoading')).toEqual(true); - }); - - it('renders value correctly', () => { - setMockValues({ ...values, searchQuery: 'hello world' }); - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('hello world'); - }); - - it('calls search on input change', () => { - const wrapper = shallow(); - wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'lorem ipsum' } }); - - expect(actions.search).toHaveBeenCalledWith('lorem ipsum'); - }); - }); - - describe('search results', () => { - it('renders an empty state', () => { - setMockValues({ ...values, searchResults: [] }); - const wrapper = shallow(); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - expect(wrapper.find(CurationResult)).toHaveLength(0); - }); - - it('renders a result component for each item in searchResults', () => { - setMockValues({ - ...values, - searchResults: [ - { id: { raw: 'doc-1' } }, - { id: { raw: 'doc-2' } }, - { id: { raw: 'doc-3' } }, - ], - }); - const wrapper = shallow(); - - expect(wrapper.find(CurationResult)).toHaveLength(3); - }); - - describe('actions', () => { - it('renders a hide result button if the document ID is not already in the hiddenIds list', () => { - setMockValues({ - ...values, - searchResults: [{ id: { raw: 'visible-document' } }], - hiddenIds: ['hidden-document'], - }); - const wrapper = shallow(); - wrapper.find(CurationResult).prop('actions')[0].onClick(); - - expect(actions.addHiddenId).toHaveBeenCalledWith('visible-document'); - }); - - it('renders a show result button if the document ID is already in the hiddenIds list', () => { - setMockValues({ - ...values, - searchResults: [{ id: { raw: 'hidden-document' } }], - hiddenIds: ['hidden-document'], - }); - const wrapper = shallow(); - wrapper.find(CurationResult).prop('actions')[0].onClick(); - - expect(actions.removeHiddenId).toHaveBeenCalledWith('hidden-document'); - }); - - it('renders a promote result button if the document ID is not already in the promotedIds list', () => { - setMockValues({ - ...values, - searchResults: [{ id: { raw: 'some-document' } }], - promotedIds: ['promoted-document'], - }); - const wrapper = shallow(); - wrapper.find(CurationResult).prop('actions')[1].onClick(); - - expect(actions.addPromotedId).toHaveBeenCalledWith('some-document'); - }); - - it('renders a demote result button if the document ID is already in the promotedIds list', () => { - setMockValues({ - ...values, - searchResults: [{ id: { raw: 'promoted-document' } }], - promotedIds: ['promoted-document'], - }); - const wrapper = shallow(); - wrapper.find(CurationResult).prop('actions')[1].onClick(); - - expect(actions.removePromotedId).toHaveBeenCalledWith('promoted-document'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.tsx deleted file mode 100644 index adee89181d43f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_flyout.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiPortal, - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiTitle, - EuiText, - EuiSpacer, - EuiFieldSearch, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { FlashMessages } from '../../../../../shared/flash_messages'; - -import { SearchLogic } from '../../../search'; -import { - RESULT_ACTIONS_DIRECTIONS, - PROMOTE_DOCUMENT_ACTION, - DEMOTE_DOCUMENT_ACTION, - HIDE_DOCUMENT_ACTION, - SHOW_DOCUMENT_ACTION, -} from '../../constants'; -import { CurationLogic } from '../curation_logic'; - -import { AddResultLogic, CurationResult } from '.'; - -export const AddResultFlyout: React.FC = () => { - const searchLogic = SearchLogic({ id: 'add-results-flyout' }); - const { searchQuery, searchResults, searchDataLoading } = useValues(searchLogic); - const { closeFlyout } = useActions(AddResultLogic); - const { search } = useActions(searchLogic); - - const { promotedIds, hiddenIds } = useValues(CurationLogic); - const { addPromotedId, removePromotedId, addHiddenId, removeHiddenId } = - useActions(CurationLogic); - - return ( - - - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.title', { - defaultMessage: 'Add result to curation', - })} -

-
- -

{RESULT_ACTIONS_DIRECTIONS}

-
-
- }> - search(e.target.value)} - isLoading={searchDataLoading} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder', - { defaultMessage: 'Search engine documents' } - )} - fullWidth - autoFocus - /> - - - {searchResults.length > 0 ? ( - - {searchResults.map((result, index) => { - const id = result.id.raw; - const isPromoted = promotedIds.includes(id); - const isHidden = hiddenIds.includes(id); - - return ( - - removeHiddenId(id), - } - : { - ...HIDE_DOCUMENT_ACTION, - onClick: () => addHiddenId(id), - }, - isPromoted - ? { - ...DEMOTE_DOCUMENT_ACTION, - onClick: () => removePromotedId(id), - } - : { - ...PROMOTE_DOCUMENT_ACTION, - onClick: () => addPromotedId(id), - }, - ]} - /> - - ); - })} - - ) : ( - - )} - -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.test.ts deleted file mode 100644 index a1f485ac2503e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 '../../../../__mocks__/engine_logic.mock'; - -import { AddResultLogic } from '.'; - -describe('AddResultLogic', () => { - const { mount } = new LogicMounter(AddResultLogic); - - const DEFAULT_VALUES = { - isFlyoutOpen: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(AddResultLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('openFlyout', () => { - it('sets isFlyoutOpen to true and resets the searchQuery term', () => { - mount({ isFlyoutOpen: false, searchQuery: 'a previous search' }); - - AddResultLogic.actions.openFlyout(); - - expect(AddResultLogic.values).toEqual({ - ...DEFAULT_VALUES, - isFlyoutOpen: true, - }); - }); - }); - - describe('closeFlyout', () => { - it('sets isFlyoutOpen to false', () => { - mount({ isFlyoutOpen: true }); - - AddResultLogic.actions.closeFlyout(); - - expect(AddResultLogic.values).toEqual({ - ...DEFAULT_VALUES, - isFlyoutOpen: false, - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.ts deleted file mode 100644 index bcf18aa9625d7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_logic.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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'; - -interface AddResultValues { - isFlyoutOpen: boolean; -} - -interface AddResultActions { - openFlyout(): void; - closeFlyout(): void; -} - -export const AddResultLogic = kea>({ - path: ['enterprise_search', 'app_search', 'curation_add_result_logic'], - actions: () => ({ - openFlyout: true, - closeFlyout: true, - }), - reducers: () => ({ - isFlyoutOpen: [ - false, - { - openFlyout: () => true, - closeFlyout: () => false, - }, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.test.tsx deleted file mode 100644 index 4540735d56218..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { Result } from '../../../result'; - -import { CurationResult } from '.'; - -describe('CurationResult', () => { - const values = { - isMetaEngine: false, - engine: { schema: 'some mock schema' }, - }; - - const mockResult = { - id: { raw: 'test' }, - _meta: { engine: 'some-engine', id: 'test' }, - }; - const mockActions = [ - { title: 'add', iconType: 'plus', onClick: () => {} }, - { title: 'remove', iconType: 'minus', onClick: () => {} }, - ]; - const mockDragging = {} as DraggableProvidedDragHandleProps; // Passed from EuiDraggable - - let wrapper: ShallowWrapper; - - beforeAll(() => { - setMockValues(values); - wrapper = shallow( - - ); - }); - - it('passes EngineLogic state', () => { - expect(wrapper.find(Result).prop('isMetaEngine')).toEqual(false); - expect(wrapper.find(Result).prop('schemaForTypeHighlights')).toEqual('some mock schema'); - }); - - it('passes result, actions, and dragHandleProps props', () => { - expect(wrapper.find(Result).prop('result')).toEqual(mockResult); - expect(wrapper.find(Result).prop('actions')).toEqual(mockActions); - expect(wrapper.find(Result).prop('dragHandleProps')).toEqual(mockDragging); - }); - - it('increments the result index before passing it on', () => { - wrapper = shallow( - - ); - expect(wrapper.find(Result).prop('resultPosition')).toEqual(6); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx deleted file mode 100644 index 12957de015891..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; - -import { useValues } from 'kea'; - -import type { SearchResult } from '@elastic/search-ui'; - -import { EngineLogic } from '../../../engine'; -import { Result } from '../../../result'; -import { ResultAction } from '../../../result/types'; - -interface Props { - actions: ResultAction[]; - dragHandleProps?: DraggableProvidedDragHandleProps | null; - result: SearchResult; - index?: number; -} - -export const CurationResult: React.FC = ({ actions, dragHandleProps, result, index }) => { - const { - isMetaEngine, - engine: { schema }, - } = useValues(EngineLogic); - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts deleted file mode 100644 index 8de177ba587ce..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { AddResultLogic } from './add_result_logic'; -export { AddResultFlyout } from './add_result_flyout'; -export { AddResultButton } from './add_result_button'; -export { CurationResult } from './curation_result'; -export { convertToResultFormat } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts deleted file mode 100644 index 2aabb2760695e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { convertToResultFormat, convertIdToMeta } from './utils'; - -describe('convertToResultFormat', () => { - it('converts curation results to a format that the Result component can use', () => { - expect( - convertToResultFormat({ - id: 'some-id', - someField: 'some flat string', - anotherField: '123456', - }) - ).toEqual({ - _meta: { - id: 'some-id', - }, - id: { - raw: 'some-id', - }, - someField: { - raw: 'some flat string', - }, - anotherField: { - raw: '123456', - }, - }); - }); -}); - -describe('convertIdToMeta', () => { - it('creates an approximate _meta object based on the curation result ID', () => { - expect(convertIdToMeta('some-id')).toEqual({ id: 'some-id' }); - expect(convertIdToMeta('some-engine|some-id')).toEqual({ - id: 'some-id', - engine: 'some-engine', - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts deleted file mode 100644 index fd5b98a02ff08..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 type { SearchResult } from '@elastic/search-ui'; - -import { ResultMeta } from '../../../result/types'; -import { CurationResult } from '../../types'; - -/** - * The `promoted` and `hidden` keys from the internal curations endpoints - * currently return a document data structure that our Result component can't - * correctly parse - we need to attempt to naively transform the data in order - * to display it in a Result - * - * TODO: Ideally someday we can update our internal curations endpoint to return - * the same Result-ready data structure that the `organic` endpoint uses, and - * remove this file when that happens - */ - -const mergeMetas = (partialMeta: ResultMeta, secondPartialMeta: ResultMeta): ResultMeta => { - return { - ...(partialMeta || {}), - ...secondPartialMeta, - }; -}; - -const isNestedObject = (value: unknown): boolean => { - if (Array.isArray(value)) { - return value.reduce( - (isNested: boolean, currentValue) => isNested || isNestedObject(currentValue), - false - ); - } - - return value === null || typeof value === 'object'; -}; - -export const convertToResultFormat = (document: CurationResult): SearchResult => { - // Convert `key: 'value'` into `key: { raw: 'value' }` - const result = Object.entries(document).reduce((acc, [key, value]) => { - acc[key] = - isNestedObject(value) || (typeof value === 'object' && Object.hasOwn(value, 'raw')) - ? value - : { raw: value }; - return acc; - }, {} as SearchResult); - - result._meta = mergeMetas(result._meta, convertIdToMeta(document.id)); - - return result; -}; - -export const convertIdToMeta = (id: string): SearchResult['_meta'] => { - const splitId = id.split('|'); - const isMetaEngine = splitId.length > 1; - - return isMetaEngine - ? { - engine: splitId[0], - id: splitId[1], - } - : ({ id } as SearchResult['_meta']); - // Note: We're casting this as _meta even though `engine` is missing, - // since for source engines the engine shouldn't matter / be displayed, - // but if needed we could likely populate this from EngineLogic.values -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx deleted file mode 100644 index ef1077e4dc9a3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 '../../../__mocks__/engine_logic.mock'; - -import { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { set } from '@kbn/safer-lodash-set/fp'; - -import { SuggestionsCallout } from '../components/suggestions_callout'; - -import { SuggestedDocumentsCallout } from './suggested_documents_callout'; - -const MOCK_VALUES = { - // CurationLogic - curation: { - suggestion: { - status: 'pending', - updated_at: '2021-01-01T00:30:00Z', - }, - queries: ['some query'], - }, - // EngineLogic - engine: { - adaptive_relevance_suggestions_active: true, - }, -}; - -describe('SuggestedDocumentsCallout', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(SuggestionsCallout)); - }); - - it('is empty when the suggestion is undefined', () => { - setMockValues({ ...MOCK_VALUES, curation: {} }); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('is empty when suggestions are not active', () => { - const values = set('engine.adaptive_relevance_suggestions_active', false, MOCK_VALUES); - setMockValues(values); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('is empty when curation status is not pending', () => { - const values = set('curation.suggestion.status', 'applied', MOCK_VALUES); - setMockValues(values); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx deleted file mode 100644 index cd9b57651c00a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/suggested_documents_callout.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { i18n } from '@kbn/i18n'; - -import { ENGINE_CURATION_SUGGESTION_PATH } from '../../../routes'; -import { EngineLogic, generateEnginePath } from '../../engine'; - -import { SuggestionsCallout } from '../components/suggestions_callout'; - -import { CurationLogic } from '.'; - -export const SuggestedDocumentsCallout: React.FC = () => { - const { - curation: { suggestion, queries }, - } = useValues(CurationLogic); - const { - engine: { adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive }, - } = useValues(EngineLogic); - - if ( - typeof suggestion === 'undefined' || - suggestion.status !== 'pending' || - adaptiveRelevanceSuggestionsActive === false - ) { - return null; - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts deleted file mode 100644 index 80719bf829a21..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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, - mockHttpValues, - mockKibanaValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { DEFAULT_META } from '../../../shared/constants'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { CurationsLogic } from '.'; - -describe('CurationsLogic', () => { - const { mount } = new LogicMounter(CurationsLogic); - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { clearFlashMessages, flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; - - const MOCK_CURATIONS_RESPONSE = { - meta: { - page: { - current: 1, - size: 10, - total_results: 1, - total_pages: 1, - }, - }, - results: [ - { - id: 'some-curation-id', - last_updated: 'January 1, 1970 at 12:00PM', - queries: ['some query'], - promoted: [], - hidden: [], - organic: [], - }, - ], - }; - - const DEFAULT_VALUES = { - dataLoading: true, - curations: [], - meta: DEFAULT_META, - selectedPageTab: 'overview', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(CurationsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onCurationsLoad', () => { - it('should set curations and meta state, & dataLoading to false', () => { - mount(); - - CurationsLogic.actions.onCurationsLoad(MOCK_CURATIONS_RESPONSE); - - expect(CurationsLogic.values).toEqual({ - ...DEFAULT_VALUES, - curations: MOCK_CURATIONS_RESPONSE.results, - meta: MOCK_CURATIONS_RESPONSE.meta, - dataLoading: false, - }); - }); - }); - - describe('onPaginate', () => { - it('should set meta.page.current state', () => { - mount(); - - CurationsLogic.actions.onPaginate(3); - - expect(CurationsLogic.values).toEqual({ - ...DEFAULT_VALUES, - meta: { page: { ...DEFAULT_VALUES.meta.page, current: 3 } }, - }); - }); - }); - - describe('onSelectPageTab', () => { - it('should set the selected page tab and clear flash messages', () => { - mount(); - - CurationsLogic.actions.onSelectPageTab('settings'); - - expect(CurationsLogic.values).toEqual({ - ...DEFAULT_VALUES, - selectedPageTab: 'settings', - }); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - }); - }); - - describe('listeners', () => { - describe('loadCurations', () => { - it('should make an API call and set curations & meta state', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_CURATIONS_RESPONSE)); - mount(); - jest.spyOn(CurationsLogic.actions, 'onCurationsLoad'); - - CurationsLogic.actions.loadCurations(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations', - { - query: { - 'page[current]': 1, - 'page[size]': 10, - }, - } - ); - expect(CurationsLogic.actions.onCurationsLoad).toHaveBeenCalledWith( - MOCK_CURATIONS_RESPONSE - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - CurationsLogic.actions.loadCurations(); - }); - }); - - describe('deleteCuration', () => { - const confirmSpy = jest.spyOn(window, 'confirm'); - - beforeEach(() => { - confirmSpy.mockImplementation(jest.fn(() => true)); - }); - - it('should make an API call and show a success message', async () => { - http.delete.mockReturnValueOnce(Promise.resolve({})); - mount(); - jest.spyOn(CurationsLogic.actions, 'loadCurations'); - - CurationsLogic.actions.deleteCuration('some-curation-id'); - expect(clearFlashMessages).toHaveBeenCalled(); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations/some-curation-id' - ); - expect(CurationsLogic.actions.loadCurations).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalledWith('Your curation was deleted'); - }); - - it('handles errors', async () => { - http.delete.mockReturnValueOnce(Promise.reject('error')); - mount(); - - CurationsLogic.actions.deleteCuration('some-curation-id'); - expect(clearFlashMessages).toHaveBeenCalled(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - - it('does nothing if the user cancels the confirmation prompt', async () => { - confirmSpy.mockImplementationOnce(() => false); - mount(); - - CurationsLogic.actions.deleteCuration('some-curation-id'); - expect(clearFlashMessages).toHaveBeenCalled(); - await nextTick(); - - expect(http.delete).not.toHaveBeenCalled(); - }); - }); - - describe('createCuration', () => { - it('should make an API call and navigate to the new curation', async () => { - http.post.mockReturnValueOnce(Promise.resolve({ id: 'some-cur-id' })); - mount(); - - CurationsLogic.actions.createCuration(['some query']); - expect(clearFlashMessages).toHaveBeenCalled(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/curations', - { - body: '{"queries":["some query"]}', - } - ); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/some-cur-id'); - }); - - it('handles errors', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); - mount(); - - CurationsLogic.actions.createCuration(['some query']); - expect(clearFlashMessages).toHaveBeenCalled(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts deleted file mode 100644 index 65e6390d68ff2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; -import { DEFAULT_META } from '../../../shared/constants'; -import { - clearFlashMessages, - flashSuccessToast, - flashAPIErrors, -} from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; -import { updateMetaPageIndex } from '../../../shared/table_pagination'; -import { ENGINE_CURATION_PATH } from '../../routes'; -import { EngineLogic, generateEnginePath } from '../engine'; - -import { DELETE_CONFIRMATION_MESSAGE, DELETE_SUCCESS_MESSAGE } from './constants'; -import { Curation, CurationsAPIResponse } from './types'; - -type CurationsPageTabs = 'overview' | 'settings' | 'history'; - -interface CurationsValues { - dataLoading: boolean; - curations: Curation[]; - meta: Meta; - selectedPageTab: CurationsPageTabs; -} - -interface CurationsActions { - onCurationsLoad(response: CurationsAPIResponse): CurationsAPIResponse; - onPaginate(newPageIndex: number): { newPageIndex: number }; - loadCurations(): void; - deleteCuration(id: string): string; - createCuration(queries: Curation['queries']): Curation['queries']; - onSelectPageTab(pageTab: CurationsPageTabs): { pageTab: CurationsPageTabs }; -} - -export const CurationsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'curations_logic'], - actions: () => ({ - onCurationsLoad: ({ results, meta }) => ({ results, meta }), - onPaginate: (newPageIndex) => ({ newPageIndex }), - loadCurations: true, - deleteCuration: (id) => id, - createCuration: (queries) => queries, - onSelectPageTab: (pageTab) => ({ pageTab }), - }), - reducers: () => ({ - selectedPageTab: [ - 'overview', - { - // @ts-expect-error upgrade typescript v5.1.6 - onSelectPageTab: (_, { pageTab }) => pageTab, - }, - ], - dataLoading: [ - true, - { - onCurationsLoad: () => false, - }, - ], - curations: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationsLoad: (_, { results }) => results, - }, - ], - meta: [ - DEFAULT_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationsLoad: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - }), - listeners: ({ actions, values }) => ({ - loadCurations: async () => { - const { meta } = values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/curations`, - { - query: { - 'page[current]': meta.page.current, - 'page[size]': meta.page.size, - }, - } - ); - actions.onCurationsLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - deleteCuration: async (id) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - clearFlashMessages(); - - if (window.confirm(DELETE_CONFIRMATION_MESSAGE)) { - try { - await http.delete(`/internal/app_search/engines/${engineName}/curations/${id}`); - actions.loadCurations(); - flashSuccessToast(DELETE_SUCCESS_MESSAGE); - } catch (e) { - flashAPIErrors(e); - } - } - }, - createCuration: async (queries) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { navigateToUrl } = KibanaLogic.values; - clearFlashMessages(); - - try { - const response = await http.post<{ id: string }>( - `/internal/app_search/engines/${engineName}/curations`, - { body: JSON.stringify({ queries }) } - ); - navigateToUrl(generateEnginePath(ENGINE_CURATION_PATH, { curationId: response.id })); - } catch (e) { - flashAPIErrors(e); - } - }, - onSelectPageTab: () => { - clearFlashMessages(); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx deleted file mode 100644 index 13fa9a0a4eabb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { CurationsRouter } from '.'; - -describe('CurationsRouter', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(Routes)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(4); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx deleted file mode 100644 index dda148ebf4d07..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { Routes, Route } from '@kbn/shared-ux-router'; - -import { - ENGINE_CURATIONS_PATH, - ENGINE_CURATIONS_NEW_PATH, - ENGINE_CURATION_PATH, - ENGINE_CURATION_SUGGESTION_PATH, -} from '../../routes'; - -import { Curation } from './curation'; -import { Curations, CurationCreation, CurationSuggestion } from './views'; - -export const CurationsRouter: React.FC = () => { - return ( - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts deleted file mode 100644 index 1db4adc7becc8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { CURATIONS_TITLE } from './constants'; -export { CurationsRouter } from './curations_router'; -export { CurationsLogic } from './curations_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts deleted file mode 100644 index 9d4497e5d72a8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 type { SearchResult } from '@elastic/search-ui'; - -import { Meta } from '../../../../../common/types'; -import { ResultMeta, SimpleFieldValue } from '../result/types'; - -export interface CurationSuggestion { - query: string; - updated_at: string; - promoted: string[]; - status: 'pending' | 'applied' | 'automated' | 'rejected' | 'disabled'; - curation_id?: string; // The id of an existing curation that this suggestion would affect - operation: 'create' | 'update' | 'delete'; - override_manual_curation?: boolean; -} - -// A curation suggestion with linked ids hydrated with actual values -export interface HydratedCurationSuggestion - extends Omit { - organic: Curation['organic']; - promoted: Curation['promoted']; - curation?: Curation; -} - -export interface Curation { - id: string; - last_updated: string; - queries: string[]; - promoted: CurationResult[]; - hidden: CurationResult[]; - organic?: SearchResult[]; // this field is missing if there are 0 results - suggestion?: CurationSuggestion; -} - -export interface CurationsAPIResponse { - results: Curation[]; - meta: Meta; -} - -export interface CurationResult { - // TODO: Consider updating our internal API to return more standard Result data in the future - id: string; - _meta?: ResultMeta; - [key: string]: SimpleFieldValue | ResultMeta | CurationResultNestedFieldValue | undefined; -} - -type CurationResultNestedFieldValue = - | { [key: string]: SimpleFieldValue | CurationResultNestedFieldValue } - | CurationResultNestedFieldValue[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts deleted file mode 100644 index 02641b09255e5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 '../../__mocks__/engine_logic.mock'; - -import { getCurationsBreadcrumbs, convertToDate, addDocument, removeDocument } from './utils'; - -describe('getCurationsBreadcrumbs', () => { - it('generates curation-prefixed breadcrumbs', () => { - expect(getCurationsBreadcrumbs()).toEqual(['Engines', 'some-engine', 'Curations']); - expect(getCurationsBreadcrumbs(['Some page'])).toEqual([ - 'Engines', - 'some-engine', - 'Curations', - 'Some page', - ]); - }); -}); - -describe('convertToDate', () => { - it('converts the English-only server timestamps to a parseable Date', () => { - const serverDateString = 'January 01, 1970 at 12:00PM'; - const date = convertToDate(serverDateString); - - expect(date).toBeInstanceOf(Date); - expect(date.getFullYear()).toEqual(1970); - }); -}); - -describe('addDocument', () => { - it('adds a new document to the end of the document array without mutating the original array', () => { - const originalDocuments = ['hello']; - const newDocuments = addDocument(originalDocuments, 'world'); - - expect(newDocuments).toEqual(['hello', 'world']); - expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array - }); -}); - -describe('removeDocument', () => { - it('removes a specific document from the array without mutating the original array', () => { - const originalDocuments = ['lorem', 'ipsum', 'dolor', 'sit', 'amet']; - const newDocuments = removeDocument(originalDocuments, 'dolor'); - - expect(newDocuments).toEqual(['lorem', 'ipsum', 'sit', 'amet']); - expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts deleted file mode 100644 index 978b63885fbdd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs'; -import { getEngineBreadcrumbs } from '../engine'; - -import { CURATIONS_TITLE } from './constants'; - -export const getCurationsBreadcrumbs = (breadcrumbs: BreadcrumbTrail = []) => - getEngineBreadcrumbs([CURATIONS_TITLE, ...breadcrumbs]); - -// The server API feels us an English datestring, but we want to convert -// it to an actual Date() instance so that we can localize date formats. -export const convertToDate = (serverDateString: string): Date => { - const readableDateString = serverDateString - .replace(' at ', ' ') - .replace('PM', ' PM') - .replace('AM', ' AM'); - return new Date(readableDateString); -}; - -export const addDocument = (documentArray: string[], newDocument: string) => { - return [...documentArray, newDocument]; -}; - -export const removeDocument = (documentArray: string[], deletedDocument: string) => { - const newArray = [...documentArray]; - const indexToDelete = newArray.indexOf(deletedDocument); - newArray.splice(indexToDelete, 1); - return newArray; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.test.tsx deleted file mode 100644 index 33aab9943cc83..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { MultiInputRows } from '../../multi_input_rows'; - -import { CurationCreation } from './curation_creation'; - -describe('CurationCreation', () => { - const actions = { - createCuration: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(MultiInputRows)).toHaveLength(1); - }); - - it('calls createCuration on submit', () => { - const wrapper = shallow(); - wrapper.find(MultiInputRows).simulate('submit', ['some query']); - - expect(actions.createCuration).toHaveBeenCalledWith(['some query']); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx deleted file mode 100644 index 6cd475a125cb2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_creation.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiPanel, EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CurationsLogic } from '..'; -import { AppSearchPageTemplate } from '../../layout'; -import { MultiInputRows } from '../../multi_input_rows'; - -import { - CREATE_NEW_CURATION_TITLE, - QUERY_INPUTS_BUTTON, - QUERY_INPUTS_PLACEHOLDER, -} from '../constants'; -import { getCurationsBreadcrumbs } from '../utils'; - -export const CurationCreation: React.FC = () => { - const { createCuration } = useActions(CurationsLogic); - - return ( - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesTitle', - { defaultMessage: 'Curation queries' } - )} -

-
- -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.create.curationQueriesDescription', - { - defaultMessage: - 'Add one or multiple queries to curate. You will be able add or remove more queries later.', - } - )} -

-
- - createCuration(queries)} - /> -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx deleted file mode 100644 index 62cbc479f1d8e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { CurationActionBar } from './curation_action_bar'; - -describe('CurationActionBar', () => { - const actions = { - acceptSuggestion: jest.fn(), - rejectSuggestion: jest.fn(), - }; - - beforeAll(() => { - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - wrapper.find('[data-test-subj="rejectButton"]').simulate('click'); - expect(actions.rejectSuggestion).toHaveBeenCalled(); - - wrapper.find('[data-test-subj="acceptButton"]').simulate('click'); - expect(actions.acceptSuggestion).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx deleted file mode 100644 index 1d16ee88571ea..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_action_bar.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CurationActionsPopover } from './curation_actions_popover'; -import { CurationSuggestionLogic } from './curation_suggestion_logic'; - -export const CurationActionBar: React.FC = () => { - const { acceptSuggestion, rejectSuggestion } = useActions(CurationSuggestionLogic); - - return ( - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.title', - { defaultMessage: 'Manage suggestion' } - )} -

-
-
- - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.rejectButtonLabel', - { defaultMessage: 'Reject' } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.acceptButtonLabel', - { defaultMessage: 'Accept' } - )} - - - - - - - -
-
-
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx deleted file mode 100644 index c5caff68bc0fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { CurationActionsPopover } from './curation_actions_popover'; - -describe('CurationActionsPopover', () => { - const actions = { - acceptSuggestion: jest.fn(), - acceptAndAutomateSuggestion: jest.fn(), - rejectSuggestion: jest.fn(), - rejectAndDisableSuggestion: jest.fn(), - }; - - beforeAll(() => { - setMockActions(actions); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - - wrapper.find('[data-test-subj="acceptButton"]').simulate('click'); - expect(actions.acceptSuggestion).toHaveBeenCalled(); - - wrapper.find('[data-test-subj="automateButton"]').simulate('click'); - expect(actions.acceptAndAutomateSuggestion).toHaveBeenCalled(); - - wrapper.find('[data-test-subj="rejectButton"]').simulate('click'); - expect(actions.rejectSuggestion).toHaveBeenCalled(); - - wrapper.find('[data-test-subj="turnoffButton"]').simulate('click'); - expect(actions.rejectAndDisableSuggestion).toHaveBeenCalled(); - }); - - it('can open and close', () => { - const wrapper = shallow(); - - expect(wrapper.prop('isOpen')).toBe(false); - - const button = shallow(wrapper.prop('button')); - button.simulate('click'); - - expect(wrapper.prop('isOpen')).toBe(true); - - wrapper.prop('closePopover')(); - - expect(wrapper.prop('isOpen')).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx deleted file mode 100644 index 0ae923830cd78..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_actions_popover.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useActions } from 'kea'; - -import { - EuiButtonIcon, - EuiListGroup, - EuiListGroupItem, - EuiPopover, - EuiPopoverTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CurationSuggestionLogic } from './curation_suggestion_logic'; - -export const CurationActionsPopover: React.FC = () => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const { - acceptSuggestion, - acceptAndAutomateSuggestion, - rejectSuggestion, - rejectAndDisableSuggestion, - } = useActions(CurationSuggestionLogic); - - const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen); - const closePopover = () => setIsPopoverOpen(false); - - const button = ( - - ); - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.actionsPopoverTitle', - { - defaultMessage: 'Manage suggestion', - } - )} - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.scss deleted file mode 100644 index 65ed80d75c78d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.scss +++ /dev/null @@ -1,24 +0,0 @@ -.curationResultPanel { - border-radius: $euiSizeM; - margin-top: $euiSizeS; - padding: $euiSizeXS; -} - -.curationResultPanel--current, .curationResultPanel--promoted { - border: $euiBorderWidthThick solid $euiColorPrimary; - background-color: tintOrShade($euiColorPrimary, 90%, 70%); // Copied from @elastit/eui/src/global_styling/variables/_panels.scss -} - -.curationResultPanel--suggested { - border: $euiBorderWidthThick solid $euiColorSuccess; - background-color: tintOrShade($euiColorSuccess, 90%, 70%); // Copied from @elastit/eui/src/global_styling/variables/_panels.scss -} - -.curationResultPanel--hidden { - border: $euiBorderWidthThick solid $euiColorAccent; - background-color: tintOrShade($euiColorAccent, 90%, 70%); // Copied from @elastit/eui/src/global_styling/variables/_panels.scss -} - -.curationResultPanel__header { - flex-grow: 0; -} \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.test.tsx deleted file mode 100644 index f1f176b9a86b0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { Result } from '../../../result'; - -import { CurationResultPanel } from './curation_result_panel'; - -describe('CurationResultPanel', () => { - const values = { - isMetaEngine: true, - engine: { - schema: {}, - }, - }; - - const results = [ - { - id: { raw: 'foo' }, - _meta: { engine: 'some-engine', id: 'foo' }, - }, - { - id: { raw: 'bar' }, - _meta: { engine: 'some-engine', id: 'bar' }, - }, - ]; - - beforeAll(() => { - setMockValues(values); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders results', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="suggestedText"]').exists()).toBe(false); - expect(wrapper.find(Result).length).toBe(2); - expect(wrapper.find(Result).at(0).props()).toEqual({ - result: results[0], - resultPosition: 1, - isMetaEngine: true, - schemaForTypeHighlights: values.engine.schema, - showClick: true, - }); - }); - - it('renders a no results message when there are no results', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="noResults"]').exists()).toBe(true); - expect(wrapper.find(Result).length).toBe(0); - }); - - it('renders the correct count', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="curationCount"]').prop('children')).toBe(2); - }); - - it('shows text about automation when variant is "suggested"', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="suggestedText"]').exists()).toBe(true); - }); - - it('renders the right class name for the provided variant', () => { - const wrapper = shallow(); - expect(wrapper.find('.curationResultPanel--promoted').exists()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx deleted file mode 100644 index 142d077501af2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiNotificationBadge, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import type { SearchResult } from '@elastic/search-ui'; - -import { i18n } from '@kbn/i18n'; - -import { EngineLogic } from '../../../engine'; - -import { Result } from '../../../result'; -import './curation_result_panel.scss'; - -interface Props { - variant: 'current' | 'promoted' | 'suggested' | 'hidden'; - results: SearchResult[]; -} - -export const CurationResultPanel: React.FC = ({ variant, results }) => { - const { isMetaEngine, engine } = useValues(EngineLogic); - const count = results.length; - - return ( - <> - - - {count} - - - -
- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelTitle', - { defaultMessage: 'Promoted results' } - )} -
-
-
- {variant === 'suggested' && ( - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.resultPanelDescription', - { defaultMessage: 'This curation can be automated by App Search' } - )} -

-
-
- )} -
- - 0 ? 'flexStart' : 'center'} - gutterSize="s" - direction="column" - className={`curationResultPanel curationResultPanel--${variant}`} - > - {results.length > 0 ? ( - results.map((result, index) => ( - - - - )) - ) : ( - -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noResultsMessage', - { defaultMessage: 'There are currently no promoted documents for this query' } - )} - -

-
- )} -
- - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.test.tsx deleted file mode 100644 index f20684dc1a107..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.test.tsx +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/shallow_useeffect.mock'; -import { mockUseParams } from '../../../../../__mocks__/react_router'; -import '../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt } from '@elastic/eui'; - -import { AppSearchPageTemplate } from '../../../layout'; - -import { Result } from '../../../result'; - -import { CurationResultPanel } from './curation_result_panel'; -import { CurationSuggestion } from './curation_suggestion'; - -describe('CurationSuggestion', () => { - const values = { - suggestion: { - query: 'foo', - updated_at: '2021-07-08T14:35:50Z', - promoted: [{ id: '4', foo: 'foo' }], - organic: [ - { - id: { raw: '3', snippet: null }, - foo: { raw: 'bar', snippet: null }, - _meta: { id: '3' }, - }, - ], - curation: { - promoted: [{ id: '1', foo: 'foo' }], - organic: [ - { - id: { raw: '5', snippet: null }, - foo: { raw: 'bar', snippet: null }, - _meta: { id: '5' }, - }, - { - id: { raw: '6', snippet: null }, - foo: { raw: 'bar', snippet: null }, - _meta: { id: '6' }, - }, - ], - }, - }, - isMetaEngine: true, - engine: { - schema: {}, - }, - }; - - const actions = { - loadSuggestion: jest.fn(), - }; - - beforeAll(() => { - setMockValues(values); - setMockActions(actions); - }); - - beforeEach(() => { - jest.clearAllMocks(); - mockUseParams.mockReturnValue({ query: 'foo' }); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(AppSearchPageTemplate)).toBe(true); - }); - - it('loads data on initialization', () => { - shallow(); - expect(actions.loadSuggestion).toHaveBeenCalled(); - }); - - it('shows existing promoted documents', () => { - const wrapper = shallow(); - const suggestedResultsPanel = wrapper.find(CurationResultPanel).at(0); - expect(suggestedResultsPanel.prop('results')).toEqual([ - { - id: { - raw: '1', - }, - foo: { - raw: 'foo', - }, - _meta: { - id: '1', - }, - }, - ]); - }); - - it('shows suggested promoted documents', () => { - const wrapper = shallow(); - const suggestedResultsPanel = wrapper.find(CurationResultPanel).at(1); - expect(suggestedResultsPanel.prop('results')).toEqual([ - { - id: { - raw: '4', - }, - foo: { - raw: 'foo', - }, - _meta: { - id: '4', - }, - }, - ]); - }); - - it('displays the query in the title', () => { - const wrapper = shallow(); - - expect(wrapper.prop('pageHeader').pageTitle).toEqual('foo'); - }); - - it('displays has a button to display organic results', () => { - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="organicResults"]').exists()).toBe(false); - wrapper.find('[data-test-subj="showOrganicResults"]').simulate('click'); - expect(wrapper.find('[data-test-subj="organicResults"]').exists()).toBe(true); - wrapper.find('[data-test-subj="showOrganicResults"]').simulate('click'); - expect(wrapper.find('[data-test-subj="organicResults"]').exists()).toBe(false); - }); - - it('displays proposed organic results', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="showOrganicResults"]').simulate('click'); - const resultsWrapper = wrapper.find('[data-test-subj="proposedOrganicResults"]').find(Result); - expect(resultsWrapper.length).toBe(1); - expect(resultsWrapper.find(Result).at(0).prop('result')).toEqual({ - id: { raw: '3', snippet: null }, - foo: { raw: 'bar', snippet: null }, - _meta: { id: '3' }, - }); - expect(resultsWrapper.find(Result).at(0).prop('isMetaEngine')).toEqual(true); - expect(resultsWrapper.find(Result).at(0).prop('schemaForTypeHighlights')).toEqual( - values.engine.schema - ); - }); - - it('displays current organic results', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="showOrganicResults"]').simulate('click'); - const resultWrapper = wrapper.find('[data-test-subj="currentOrganicResults"]').find(Result); - expect(resultWrapper.length).toBe(2); - expect(resultWrapper.find(Result).at(0).prop('result')).toEqual({ - id: { raw: '5', snippet: null }, - foo: { raw: 'bar', snippet: null }, - _meta: { id: '5' }, - }); - expect(resultWrapper.find(Result).at(0).prop('isMetaEngine')).toEqual(true); - expect(resultWrapper.find(Result).at(0).prop('schemaForTypeHighlights')).toEqual( - values.engine.schema - ); - }); - - it('shows an empty prompt when there are no organic results', () => { - setMockValues({ - ...values, - suggestion: { - ...values.suggestion, - organic: [], - curation: { - ...values.suggestion.curation, - organic: [], - }, - }, - }); - const wrapper = shallow(); - wrapper.find('[data-test-subj="showOrganicResults"]').simulate('click'); - expect(wrapper.find('[data-test-subj="currentOrganicResults"]').exists()).toBe(false); - expect(wrapper.find('[data-test-subj="proposedOrganicResults"]').exists()).toBe(false); - expect(wrapper.find(EuiEmptyPrompt).exists()).toBe(true); - }); - - it('renders even if no data is set yet', () => { - setMockValues({ - suggestion: null, - }); - const wrapper = shallow(); - expect(wrapper.find(AppSearchPageTemplate).exists()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx deleted file mode 100644 index bed16d609ef15..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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, { useEffect, useState } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiButtonEmpty, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import type { SearchResult } from '@elastic/search-ui'; - -import { i18n } from '@kbn/i18n'; - -import { LeafIcon } from '../../../../../shared/icons'; -import { useDecodedParams } from '../../../../utils/encode_path_params'; -import { EngineLogic } from '../../../engine'; -import { AppSearchPageTemplate } from '../../../layout'; -import { Result } from '../../../result'; -import { convertToResultFormat } from '../../curation/results'; -import { getCurationsBreadcrumbs } from '../../utils'; - -import { CurationActionBar } from './curation_action_bar'; -import { CurationResultPanel } from './curation_result_panel'; - -import { CurationSuggestionLogic } from './curation_suggestion_logic'; - -export const CurationSuggestion: React.FC = () => { - const { query } = useDecodedParams(); - const { engine, isMetaEngine } = useValues(EngineLogic); - const curationSuggestionLogic = CurationSuggestionLogic({ query }); - const { loadSuggestion } = useActions(curationSuggestionLogic); - const { suggestion, dataLoading } = useValues(curationSuggestionLogic); - const [showOrganicResults, setShowOrganicResults] = useState(false); - const currentOrganicResults = suggestion?.curation?.organic || []; - const proposedOrganicResults = suggestion?.organic || []; - const totalNumberOfOrganicResults = currentOrganicResults.length + proposedOrganicResults.length; - const existingCurationResults = suggestion?.curation - ? suggestion.curation.promoted.map(convertToResultFormat) - : []; - const suggestedPromotedDocuments = suggestion?.promoted - ? suggestion?.promoted.map(convertToResultFormat) - : []; - - const suggestionQuery = suggestion?.query || ''; - - useEffect(() => { - loadSuggestion(); - }, []); - - return ( - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.currentTitle', - { defaultMessage: 'Current' } - )} -

-
- - -
- - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.suggestionTitle', - { defaultMessage: 'Suggested' } - )} -

-
- - -
-
- - - setShowOrganicResults(!showOrganicResults)} - data-test-subj="showOrganicResults" - > - {showOrganicResults - ? i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.collapseButtonLabel', - { defaultMessage: 'Collapse organic search results' } - ) - : i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.expandButtonLabel', - { defaultMessage: 'Expand organic search results' } - )} - - {showOrganicResults && totalNumberOfOrganicResults === 0 && ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsTitle', - { defaultMessage: 'No results' } - )} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.noOrganicResultsDescription', - { defaultMessage: 'No organic search results were returned for this query' } - )} - /> - )} - {showOrganicResults && totalNumberOfOrganicResults > 0 && ( - <> - - - - - {currentOrganicResults.length > 0 && ( - - {currentOrganicResults.map((result: SearchResult, index) => ( - - - - ))} - - )} - - - {proposedOrganicResults.length > 0 && ( - - {proposedOrganicResults.map((result: SearchResult, index) => ( - - - - ))} - - )} - - - - - )} - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts deleted file mode 100644 index 8b20c6a64e073..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts +++ /dev/null @@ -1,524 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, - mockKibanaValues, -} from '../../../../../__mocks__/kea_logic'; - -import '../../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; -import { HydratedCurationSuggestion } from '../../types'; - -import { CurationSuggestionLogic } from './curation_suggestion_logic'; - -const DEFAULT_VALUES = { - dataLoading: true, - suggestion: null, -}; - -const suggestion: HydratedCurationSuggestion = { - query: 'foo', - updated_at: '2021-07-08T14:35:50Z', - promoted: [ - { - id: '1', - }, - { - id: '2', - }, - { - id: '3', - }, - ], - status: 'pending', - operation: 'create', - curation: { - id: 'cur-6155e69c7a2f2e4f756303fd', - queries: ['foo'], - promoted: [ - { - id: '5', - }, - ], - hidden: [], - last_updated: 'September 30, 2021 at 04:32PM', - organic: [ - { - id: { - raw: '1', - }, - _meta: { - id: '1', - engine: 'some-engine', - }, - }, - ], - }, - organic: [ - { - id: { - raw: '1', - }, - _meta: { - id: '1', - engine: 'some-engine', - }, - }, - { - id: { - raw: '2', - }, - _meta: { - id: '2', - engine: 'some-engine', - }, - }, - ], -}; - -const MOCK_RESPONSE = { - query: 'foo', - status: 'pending', - updated_at: '2021-07-08T14:35:50Z', - operation: 'create', - suggestion: { - promoted: [ - { - id: '1', - }, - { - id: '2', - }, - { - id: '3', - }, - ], - organic: [ - { - id: { - raw: '1', - }, - _meta: { - id: '1', - engine: 'some-engine', - }, - }, - { - id: { - raw: '2', - }, - _meta: { - id: '2', - engine: 'some-engine', - }, - }, - ], - }, - curation: { - id: 'cur-6155e69c7a2f2e4f756303fd', - queries: ['foo'], - promoted: [ - { - id: '5', - }, - ], - hidden: [], - last_updated: 'September 30, 2021 at 04:32PM', - organic: [ - { - id: { - raw: '1', - }, - _meta: { - id: '1', - engine: 'some-engine', - }, - }, - ], - }, -}; - -describe('CurationSuggestionLogic', () => { - const { mount } = new LogicMounter(CurationSuggestionLogic); - const { setErrorMessage, setQueuedErrorMessage } = mockFlashMessageHelpers; - const { navigateToUrl } = mockKibanaValues; - - const mountLogic = (props: object = {}) => { - mount(props, { query: 'foo-query' }); - }; - - const { http } = mockHttpValues; - - const itHandlesInlineErrors = (callback: () => void) => { - it('handles inline errors', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - error: 'error', - }, - ], - }) - ); - mountLogic({ - suggestion, - }); - - callback(); - await nextTick(); - - expect(setErrorMessage).toHaveBeenCalledWith('error'); - }); - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mountLogic(); - expect(CurationSuggestionLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onSuggestionLoaded', () => { - it('should save provided state and set dataLoading to false', () => { - mountLogic(); - CurationSuggestionLogic.actions.onSuggestionLoaded({ - suggestion, - }); - expect(CurationSuggestionLogic.values).toEqual({ - ...DEFAULT_VALUES, - suggestion, - dataLoading: false, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadSuggestion', () => { - it('should set dataLoading state', () => { - mountLogic({ dataLoading: false }); - - CurationSuggestionLogic.actions.loadSuggestion(); - - expect(CurationSuggestionLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make API calls to fetch data and trigger onSuggestionLoaded', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mountLogic(); - jest.spyOn(CurationSuggestionLogic.actions, 'onSuggestionLoaded'); - - CurationSuggestionLogic.actions.loadSuggestion(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions/foo-query', - { - query: { - type: 'curation', - }, - } - ); - - expect(CurationSuggestionLogic.actions.onSuggestionLoaded).toHaveBeenCalledWith({ - suggestion, - }); - }); - - // This could happen if a user applies a suggestion and then navigates back to a detail page via - // the back button, etc. The suggestion still exists, it's just not in a "pending" state - // so we can show it.ga - it('will redirect if the suggestion is not found', async () => { - http.get.mockReturnValueOnce( - Promise.reject({ - response: { status: 404 }, - }) - ); - - mountLogic(); - CurationSuggestionLogic.actions.loadSuggestion(); - await nextTick(); - expect(setQueuedErrorMessage).toHaveBeenCalled(); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - CurationSuggestionLogic.actions.loadSuggestion(); - }); - }); - - describe('acceptSuggestion', () => { - it('will make an http call to apply the suggestion, and then navigate to that detail page', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - ...suggestion, - status: 'accepted', - curation_id: 'cur-6155e69c7a2f2e4f756303fd', - }, - ], - }) - ); - mountLogic({ - suggestion, - }); - - CurationSuggestionLogic.actions.acceptSuggestion(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify([ - { - query: 'foo', - type: 'curation', - status: 'applied', - }, - ]), - } - ); - - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/curations/cur-6155e69c7a2f2e4f756303fd' - ); - }); - - describe('when a suggestion is a "delete" suggestion', () => { - const deleteSuggestion = { - ...suggestion, - operation: 'delete', - promoted: [], - curation_id: 'cur-6155e69c7a2f2e4f756303fd', - }; - - it('will show a confirm message before applying, and redirect a user back to the curations page, rather than the curation details page', async () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(true); - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [{ ...suggestion, status: 'accepted', curation_id: undefined }], - }) - ); - mountLogic({ - suggestion: deleteSuggestion, - }); - CurationSuggestionLogic.actions.acceptSuggestion(); - await nextTick(); - - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - - it('will do nothing if the user does not confirm', async () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(false); - mountLogic({ - suggestion: deleteSuggestion, - }); - CurationSuggestionLogic.actions.acceptSuggestion(); - await nextTick(); - expect(http.put).not.toHaveBeenCalled(); - expect(navigateToUrl).not.toHaveBeenCalled(); - }); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(true); - CurationSuggestionLogic.actions.acceptSuggestion(); - }); - - itHandlesInlineErrors(() => { - CurationSuggestionLogic.actions.acceptSuggestion(); - }); - }); - - describe('acceptAndAutomateSuggestion', () => { - it('will make an http call to apply the suggestion, and then navigate to that detail page', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - ...suggestion, - status: 'accepted', - curation_id: 'cur-6155e69c7a2f2e4f756303fd', - }, - ], - }) - ); - mountLogic({ - suggestion, - }); - - CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify([ - { - query: 'foo', - type: 'curation', - status: 'automated', - }, - ]), - } - ); - - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/curations/cur-6155e69c7a2f2e4f756303fd' - ); - }); - - describe('when a suggestion is a "delete" suggestion', () => { - const deleteSuggestion = { - ...suggestion, - operation: 'delete', - promoted: [], - curation_id: 'cur-6155e69c7a2f2e4f756303fd', - }; - - it('will show a confirm message before applying, and redirect a user back to the curations page, rather than the curation details page', async () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(true); - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [{ ...suggestion, status: 'accepted', curation_id: undefined }], - }) - ); - mountLogic({ - suggestion: deleteSuggestion, - }); - CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); - await nextTick(); - - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - - it('will do nothing if the user does not confirm', async () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(false); - mountLogic({ - suggestion: deleteSuggestion, - }); - CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); - await nextTick(); - expect(http.put).not.toHaveBeenCalled(); - expect(navigateToUrl).not.toHaveBeenCalled(); - }); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(true); - CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); - }); - - itHandlesInlineErrors(() => { - CurationSuggestionLogic.actions.acceptAndAutomateSuggestion(); - }); - }); - - describe('rejectSuggestion', () => { - it('will make an http call to apply the suggestion, and then navigate back the curations page', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - ...suggestion, - status: 'rejected', - curation_id: 'cur-6155e69c7a2f2e4f756303fd', - }, - ], - }) - ); - mountLogic({ - suggestion, - }); - - CurationSuggestionLogic.actions.rejectSuggestion(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify([ - { - query: 'foo', - type: 'curation', - status: 'rejected', - }, - ]), - } - ); - - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - CurationSuggestionLogic.actions.rejectSuggestion(); - }); - - itHandlesInlineErrors(() => { - CurationSuggestionLogic.actions.rejectSuggestion(); - }); - }); - - describe('rejectAndDisableSuggestion', () => { - it('will make an http call to apply the suggestion, and then navigate back the curations page', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - ...suggestion, - status: 'disabled', - curation_id: 'cur-6155e69c7a2f2e4f756303fd', - }, - ], - }) - ); - mountLogic({ - suggestion, - }); - - CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify([ - { - query: 'foo', - type: 'curation', - status: 'disabled', - }, - ]), - } - ); - - expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations'); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); - }); - - itHandlesInlineErrors(() => { - CurationSuggestionLogic.actions.rejectAndDisableSuggestion(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts deleted file mode 100644 index a05e952573781..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.ts +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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 { HttpSetup } from '@kbn/core/public'; - -import { i18n } from '@kbn/i18n'; - -import { - flashAPIErrors, - setErrorMessage, - setQueuedErrorMessage, - setQueuedSuccessMessage, -} from '../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../shared/http'; -import { KibanaLogic } from '../../../../../shared/kibana'; -import { ENGINE_CURATIONS_PATH, ENGINE_CURATION_PATH } from '../../../../routes'; -import { EngineLogic, generateEnginePath } from '../../../engine'; -import { CurationSuggestion, HydratedCurationSuggestion } from '../../types'; - -interface APIResponseError { - error: string; -} -interface CurationSuggestionValues { - dataLoading: boolean; - suggestion: HydratedCurationSuggestion | null; -} - -interface CurationSuggestionActions { - loadSuggestion(): void; - onSuggestionLoaded({ suggestion }: { suggestion: HydratedCurationSuggestion }): { - suggestion: HydratedCurationSuggestion; - }; - acceptSuggestion(): void; - acceptAndAutomateSuggestion(): void; - rejectSuggestion(): void; - rejectAndDisableSuggestion(): void; -} - -interface CurationSuggestionProps { - query: CurationSuggestion['query']; -} - -export const CurationSuggestionLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'curations', 'suggestion_logic'], - actions: () => ({ - loadSuggestion: true, - onSuggestionLoaded: ({ suggestion }) => ({ - suggestion, - }), - acceptSuggestion: true, - acceptAndAutomateSuggestion: true, - rejectSuggestion: true, - rejectAndDisableSuggestion: true, - }), - reducers: () => ({ - dataLoading: [ - true, - { - loadSuggestion: () => true, - onSuggestionLoaded: () => false, - }, - ], - suggestion: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSuggestionLoaded: (_, { suggestion }) => suggestion, - }, - ], - }), - listeners: ({ actions, values, props }) => ({ - loadSuggestion: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const suggestionResponse = await http.get( - `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions/${props.query}`, - { - query: { - type: 'curation', - }, - } - ); - - // We pull the `organic` and `promoted` fields up to the main body of the suggestion, - // out of the nested `suggestion` field on the response - const { suggestion, ...baseSuggestion } = suggestionResponse; - const suggestionData = { - ...baseSuggestion, - promoted: suggestion.promoted, - organic: suggestion.organic, - }; - - actions.onSuggestionLoaded({ - suggestion: suggestionData, - }); - } catch (e) { - if (e.response?.status === 404) { - const message = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.notFoundError', - { - defaultMessage: - 'Could not find suggestion, it may have already been applied or rejected.', - } - ); - setQueuedErrorMessage(message); - KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - } else { - flashAPIErrors(e); - } - } - }, - acceptSuggestion: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { suggestion } = values; - - if (suggestion!.operation === 'delete') { - const confirmed = await confirmDialog('Are you sure you want to delete this curation?'); - if (!confirmed) return; - } - - try { - const updatedSuggestion = await updateSuggestion( - http, - engineName, - suggestion!.query, - 'applied' - ); - - setQueuedSuccessMessage( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAppliedMessage', - { defaultMessage: 'Suggestion was successfully applied.' } - ) - ); - if (suggestion!.operation === 'delete') { - // Because if a curation is deleted, there will be no curation detail page to navigate to afterwards. - KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - } else { - KibanaLogic.values.navigateToUrl( - generateEnginePath(ENGINE_CURATION_PATH, { - curationId: updatedSuggestion.curation_id, - }) - ); - } - } catch (e) { - if (e.message) { - setErrorMessage(e.message); - } else { - flashAPIErrors(e); - } - } - }, - acceptAndAutomateSuggestion: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { suggestion } = values; - - if (suggestion!.operation === 'delete') { - const confirmed = await confirmDialog('Are you sure you want to delete this curation?'); - if (!confirmed) return; - } - - try { - const updatedSuggestion = await updateSuggestion( - http, - engineName, - suggestion!.query, - 'automated' - ); - - setQueuedSuccessMessage( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyAutomatedMessage', - { - defaultMessage: - 'Suggestion was successfully applied and all future suggestions for the query "{query}" will be automatically applied.', - values: { query: suggestion!.query }, - } - ) - ); - if (suggestion!.operation === 'delete') { - // Because if a curation is deleted, there will be no curation detail page to navigate to afterwards. - KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - } else { - KibanaLogic.values.navigateToUrl( - generateEnginePath(ENGINE_CURATION_PATH, { - curationId: updatedSuggestion.curation_id, - }) - ); - } - } catch (e) { - if (e.message) { - setErrorMessage(e.message); - } else { - flashAPIErrors(e); - } - } - }, - rejectSuggestion: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { suggestion } = values; - - try { - await updateSuggestion(http, engineName, suggestion!.query, 'rejected'); - - setQueuedSuccessMessage( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyRejectedMessage', - { - defaultMessage: 'Suggestion was successfully rejected.', - } - ) - ); - KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - } catch (e) { - if (e.message) { - setErrorMessage(e.message); - } else { - flashAPIErrors(e); - } - } - }, - rejectAndDisableSuggestion: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const { suggestion } = values; - - try { - await updateSuggestion(http, engineName, suggestion!.query, 'disabled'); - - setQueuedSuccessMessage( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.suggestedCuration.successfullyDisabledMessage', - { - defaultMessage: - 'Suggestion was successfully rejected and you will no longer receive suggestions for the query "{query}".', - values: { query: suggestion!.query }, - } - ) - ); - KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH)); - } catch (e) { - if (e.message) { - setErrorMessage(e.message); - } else { - flashAPIErrors(e); - } - } - }, - }), -}); - -const updateSuggestion = async ( - http: HttpSetup, - engineName: string, - query: string, - status: string -) => { - const response = await http.put<{ results: Array }>( - `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, - { - body: JSON.stringify([ - { - query, - type: 'curation', - status, - }, - ]), - } - ); - - if (Object.hasOwn(response.results[0], 'error')) { - throw new Error((response.results[0] as APIResponseError).error); - } - - return response.results[0] as CurationSuggestion; -}; - -const confirmDialog = (msg: string) => { - return new Promise(function (resolve) { - const confirmed = window.confirm(msg); - return resolve(confirmed); - }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/index.ts deleted file mode 100644 index 9cb1809e20442..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { CurationSuggestion } from './curation_suggestion'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx deleted file mode 100644 index 8bc5e33263da0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/shallow_useeffect.mock'; -import '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiTab } from '@elastic/eui'; -import { set } from '@kbn/safer-lodash-set/fp'; - -import { getPageHeaderTabs, getPageTitle } from '../../../../test_helpers'; - -import { Curations } from './curations'; -import { CurationsHistory } from './curations_history/curations_history'; -import { CurationsOverview } from './curations_overview'; -import { CurationsSettings } from './curations_settings'; - -describe('Curations', () => { - const values = { - // CurationsLogic - dataLoading: false, - curations: [ - { - id: 'cur-id-1', - last_updated: 'January 1, 1970 at 12:00PM', - queries: ['hiking'], - }, - { - id: 'cur-id-2', - last_updated: 'January 2, 1970 at 12:00PM', - queries: ['mountains', 'valleys'], - }, - ], - meta: { - page: { - current: 1, - size: 10, - total_results: 2, - }, - }, - selectedPageTab: 'overview', - // CurationsSettingsLogic - curationsSettings: { - enabled: true, - }, - // EngineLogic - engine: { - adaptive_relevance_suggestions_active: true, - }, - }; - - const actions = { - loadCurations: jest.fn(), - onPaginate: jest.fn(), - onSelectPageTab: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders with a set of tabs in the page header', () => { - const wrapper = shallow(); - - expect(getPageTitle(wrapper)).toEqual('Curated results'); - - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - tabs.at(0).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(1, 'overview'); - - tabs.at(1).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(2, 'history'); - - tabs.at(2).simulate('click'); - expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(3, 'settings'); - }); - - it('renders less tabs when suggestions are not active', () => { - setMockValues(set('engine.adaptive_relevance_suggestions_active', false, values)); - const wrapper = shallow(); - - expect(getPageTitle(wrapper)).toEqual('Curated results'); - - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - expect(tabs.length).toBe(2); - }); - - it('renders an overview view', () => { - setMockValues({ ...values, selectedPageTab: 'overview' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(0).prop('isSelected')).toEqual(true); - - expect(wrapper.find(CurationsOverview)).toHaveLength(1); - }); - - it('renders a history view', () => { - setMockValues({ ...values, selectedPageTab: 'history' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(1).prop('isSelected')).toEqual(true); - - expect(wrapper.find(CurationsHistory)).toHaveLength(1); - }); - - it('renders a settings view', () => { - setMockValues({ ...values, selectedPageTab: 'settings' }); - const wrapper = shallow(); - const tabs = getPageHeaderTabs(wrapper).find(EuiTab); - - expect(tabs.at(2).prop('isSelected')).toEqual(true); - - expect(wrapper.find(CurationsSettings)).toHaveLength(1); - }); - - describe('loading state', () => { - it('renders a full-page loading state and hides tabs on initial page load', () => { - setMockValues({ ...values, dataLoading: true }); - const wrapper = shallow(); - - expect(wrapper.prop('isLoading')).toEqual(true); - expect(wrapper.prop('tabs')).toBeUndefined(); - }); - - it('does not re-render a full-page loading and shows tabs state when data is loaded', () => { - setMockValues({ ...values, dataLoading: false }); - const wrapper = shallow(); - - expect(wrapper.prop('isLoading')).toEqual(false); - expect(typeof wrapper.prop('tabs')).not.toBeUndefined(); - }); - }); - - it('calls loadCurations on page load', () => { - shallow(); - - expect(actions.loadCurations).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx deleted file mode 100644 index 5edfd1b969964..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { i18n } from '@kbn/i18n'; - -import { EuiButtonTo } from '../../../../shared/react_router_helpers'; - -import { ENGINE_CURATIONS_NEW_PATH } from '../../../routes'; -import { EngineLogic, generateEnginePath } from '../../engine'; -import { AppSearchPageTemplate } from '../../layout'; - -import { CURATIONS_OVERVIEW_TITLE, CREATE_NEW_CURATION_TITLE } from '../constants'; -import { CurationsLogic } from '../curations_logic'; -import { getCurationsBreadcrumbs } from '../utils'; - -import { CurationsHistory } from './curations_history/curations_history'; -import { CurationsOverview } from './curations_overview'; -import { CurationsSettings } from './curations_settings'; - -export const Curations: React.FC = () => { - const { dataLoading, meta, selectedPageTab } = useValues(CurationsLogic); - const { loadCurations, onSelectPageTab } = useActions(CurationsLogic); - const { - engine: { adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive }, - } = useValues(EngineLogic); - - const OVERVIEW_TAB = { - label: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.overviewPageTabLabel', - { - defaultMessage: 'Overview', - } - ), - isSelected: selectedPageTab === 'overview', - onClick: () => onSelectPageTab('overview'), - }; - - const HISTORY_TAB = { - label: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.historyPageTabLabel', { - defaultMessage: 'History', - }), - isSelected: selectedPageTab === 'history', - onClick: () => onSelectPageTab('history'), - }; - - const SETTINGS_TAB = { - label: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.settingsPageTabLabel', - { - defaultMessage: 'Settings', - } - ), - isSelected: selectedPageTab === 'settings', - onClick: () => onSelectPageTab('settings'), - }; - - const pageTabs = adaptiveRelevanceSuggestionsActive - ? [OVERVIEW_TAB, HISTORY_TAB, SETTINGS_TAB] - : [OVERVIEW_TAB, SETTINGS_TAB]; - - useEffect(() => { - loadCurations(); - }, [meta.page.current]); - - return ( - - {CREATE_NEW_CURATION_TITLE} - , - ], - tabs: dataLoading ? undefined : pageTabs, - }} - isLoading={dataLoading} - > - {selectedPageTab === 'overview' && } - {selectedPageTab === 'history' && } - {selectedPageTab === 'settings' && } - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx deleted file mode 100644 index 8e2f3e8eff0af..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -// TODO use engine_logic.mock instead of setMockValues({ engineName: 'some-engine' }), currently that breaks the test -import { setMockValues } from '../../../../../../__mocks__/kea_logic'; -// import '../../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButtonEmpty } from '@elastic/eui'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { EntSearchLogStream } from '../../../../../../shared/log_stream'; -import { DataPanel } from '../../../../data_panel'; - -import { AutomatedCurationsHistoryPanel } from './automated_curations_history_panel'; - -describe('AutomatedCurationsHistoryPanel', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues({ engineName: 'some-engine' }); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(DataPanel)).toBe(true); - expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual( - 'event.kind: event and event.dataset: search-relevance-suggestions and appsearch.adaptive_relevance.engine: some-engine and event.action: curation_suggestion and appsearch.adaptive_relevance.suggestion.new_status: automated' - ); - }); - - it('sets new endTimestamp when refresh is pressed', async () => { - const wrapper = shallow(); - const initialTimestamp = wrapper.find(EntSearchLogStream).prop('endTimestamp'); - await nextTick(); - - // Find the refresh button and click - shallow(wrapper.find(DataPanel).prop('title')).find(EuiButtonEmpty).simulate('click'); - - wrapper.update(); - - expect(wrapper.find(EntSearchLogStream).prop('endTimestamp')).not.toEqual(initialTimestamp); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx deleted file mode 100644 index 10b73874dbe7e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/automated_curations_history_panel.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID } from '../../../../../../../../common/constants'; -import { EntSearchLogStream } from '../../../../../../shared/log_stream'; -import { DataPanel } from '../../../../data_panel'; -import { EngineLogic } from '../../../../engine'; - -export const AutomatedCurationsHistoryPanel: React.FC = () => { - const { engineName } = useValues(EngineLogic); - const [endTimestamp, setEndTimestamp] = useState(Date.now()); - - const filters = [ - 'event.kind: event', - 'event.dataset: search-relevance-suggestions', - `appsearch.adaptive_relevance.engine: ${engineName}`, - 'event.action: curation_suggestion', - 'appsearch.adaptive_relevance.suggestion.new_status: automated', - ]; - - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableTitle', - { - defaultMessage: 'Adaptive relevance changes', - } - )} - - - setEndTimestamp(Date.now())} - > - {i18n.translate('xpack.enterpriseSearch.appSearch.engines.curations.refreshButton', { - defaultMessage: 'Refresh', - })} - - - - } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.automatedCurationsHistoryPanel.tableDecription', - { - defaultMessage: - 'A detailed log of recent changes to curations powered by adaptive relevance.', - } - )} - hasBorder - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts deleted file mode 100644 index 7e7702b4e5d67..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../../../../../__mocks__/kea_logic'; -import '../../../../../../__mocks__/engine_logic.mock'; -import { DEFAULT_META } from '../../../../../../../shared/constants'; -import { itShowsServerErrorAsFlashMessage } from '../../../../../../../test_helpers'; - -// I don't know why eslint is saying this line is out of order -// eslint-disable-next-line import/order -import { nextTick } from '@kbn/test-jest-helpers'; - -import { IgnoredQueriesLogic } from './ignored_queries_logic'; - -const DEFAULT_VALUES = { - dataLoading: true, - ignoredQueries: [], - meta: { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - size: 10, - }, - }, -}; - -describe('IgnoredQueriesLogic', () => { - const { mount } = new LogicMounter(IgnoredQueriesLogic); - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(IgnoredQueriesLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onIgnoredQueriesLoad', () => { - it('should set queries, meta state, & dataLoading to false', () => { - IgnoredQueriesLogic.actions.onIgnoredQueriesLoad(['first query', 'second query'], { - page: { - current: 1, - size: 10, - total_results: 1, - total_pages: 1, - }, - }); - - expect(IgnoredQueriesLogic.values).toEqual({ - ...DEFAULT_VALUES, - ignoredQueries: ['first query', 'second query'], - meta: { - page: { - current: 1, - size: 10, - total_results: 1, - total_pages: 1, - }, - }, - dataLoading: false, - }); - }); - }); - - describe('onPaginate', () => { - it('should update meta', () => { - IgnoredQueriesLogic.actions.onPaginate(2); - - expect(IgnoredQueriesLogic.values).toEqual({ - ...DEFAULT_VALUES, - meta: { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - current: 2, - }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadIgnoredQueries', () => { - it('should make an API call and set suggestions & meta state', async () => { - mount({ ...DEFAULT_VALUES, dataLoading: false }); - http.post.mockReturnValueOnce( - Promise.resolve({ - results: [{ query: 'first query' }, { query: 'second query' }], - meta: { - page: { - current: 1, - size: 10, - total_results: 1, - total_pages: 1, - }, - }, - }) - ); - jest.spyOn(IgnoredQueriesLogic.actions, 'onIgnoredQueriesLoad'); - - IgnoredQueriesLogic.actions.loadIgnoredQueries(); - expect(IgnoredQueriesLogic.values).toEqual({ ...DEFAULT_VALUES, dataLoading: true }); - - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify({ - page: { - current: 1, - size: 10, - }, - filters: { - status: ['disabled'], - type: 'curation', - }, - }), - } - ); - - expect(IgnoredQueriesLogic.actions.onIgnoredQueriesLoad).toHaveBeenCalledWith( - ['first query', 'second query'], - { - page: { - current: 1, - size: 10, - total_results: 1, - total_pages: 1, - }, - } - ); - }); - - itShowsServerErrorAsFlashMessage(http.post, () => { - mount(); - IgnoredQueriesLogic.actions.loadIgnoredQueries(); - }); - }); - - describe('allowIgnoredQuery', () => { - it('will make an http call to reject the suggestion for the query', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - query: 'test query', - type: 'curation', - status: 'rejected', - }, - ], - }) - ); - - IgnoredQueriesLogic.actions.allowIgnoredQuery('test query'); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/suggestions', - { - body: JSON.stringify([ - { - query: 'test query', - type: 'curation', - status: 'rejected', - }, - ]), - } - ); - - expect(flashSuccessToast).toHaveBeenCalledWith(expect.any(String)); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - mount(); - IgnoredQueriesLogic.actions.allowIgnoredQuery('test query'); - }); - - it('handles inline errors', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - results: [ - { - error: 'error', - }, - ], - }) - ); - - IgnoredQueriesLogic.actions.allowIgnoredQuery('test query'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts deleted file mode 100644 index 5062c933d9f29..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { Meta } from '../../../../../../../../../common/types'; -import { DEFAULT_META } from '../../../../../../../shared/constants'; -import { flashAPIErrors, flashSuccessToast } from '../../../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../../../shared/http'; -import { updateMetaPageIndex } from '../../../../../../../shared/table_pagination'; -import { EngineLogic } from '../../../../../engine'; -import { CurationSuggestion } from '../../../../types'; - -interface IgnoredQueriesValues { - dataLoading: boolean; - ignoredQueries: string[]; - meta: Meta; -} - -interface IgnoredQueriesActions { - allowIgnoredQuery(ignoredQuery: string): { - ignoredQuery: string; - }; - loadIgnoredQueries(): void; - onIgnoredQueriesLoad( - ignoredQueries: string[], - meta: Meta - ): { ignoredQueries: string[]; meta: Meta }; - onPaginate(newPageIndex: number): { newPageIndex: number }; -} - -interface SuggestionUpdateError { - error: string; -} - -const ALLOW_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.allowQuerySuccessMessage', - { - defaultMessage: 'You’ll be notified about future suggestions for this query', - } -); - -export const IgnoredQueriesLogic = kea>({ - path: ['enterprise_search', 'app_search', 'curations', 'ignored_queries_panel_logic'], - actions: () => ({ - allowIgnoredQuery: (ignoredQuery) => ({ ignoredQuery }), - loadIgnoredQueries: true, - onIgnoredQueriesLoad: (ignoredQueries, meta) => ({ ignoredQueries, meta }), - onPaginate: (newPageIndex) => ({ newPageIndex }), - }), - reducers: () => ({ - dataLoading: [ - true, - { - loadIgnoredQueries: () => true, - onIgnoredQueriesLoad: () => false, - }, - ], - ignoredQueries: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onIgnoredQueriesLoad: (_, { ignoredQueries }) => ignoredQueries, - }, - ], - meta: [ - { - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - size: 10, - }, - }, - { - // @ts-expect-error upgrade typescript v5.1.6 - onIgnoredQueriesLoad: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - }), - listeners: ({ actions, values }) => ({ - loadIgnoredQueries: async () => { - const { meta } = values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response: { results: CurationSuggestion[]; meta: Meta } = await http.post( - `/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, - { - body: JSON.stringify({ - page: { - current: meta.page.current, - size: meta.page.size, - }, - filters: { - status: ['disabled'], - type: 'curation', - }, - }), - } - ); - - const queries = response.results.map((suggestion) => suggestion.query); - actions.onIgnoredQueriesLoad(queries, response.meta); - } catch (e) { - flashAPIErrors(e); - } - }, - allowIgnoredQuery: async ({ ignoredQuery }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - try { - const response = await http.put<{ - results: Array; - }>(`/internal/app_search/engines/${engineName}/adaptive_relevance/suggestions`, { - body: JSON.stringify([ - { - query: ignoredQuery, - type: 'curation', - status: 'rejected', - }, - ]), - }); - - if (Object.hasOwn(response.results[0], 'error')) { - throw (response.results[0] as SuggestionUpdateError).error; - } - - flashSuccessToast(ALLOW_SUCCESS_MESSAGE); - // re-loading to update the current page rather than manually remove the query - actions.loadIgnoredQueries(); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_panel.test.tsx deleted file mode 100644 index c704f77371c71..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_panel.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 '../../../../../../../__mocks__/shallow_useeffect.mock'; -// I don't know why eslint is saying this line is out of order -// eslint-disable-next-line import/order -import { setMockActions, setMockValues } from '../../../../../../../__mocks__/kea_logic'; -import '../../../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBasicTable, EuiButtonEmpty } from '@elastic/eui'; - -import { DataPanel } from '../../../../../data_panel'; - -import { IgnoredQueriesPanel } from './ignored_queries_panel'; - -describe('IgnoredQueriesPanel', () => { - const values = { - dataLoading: false, - suggestions: [ - { - query: 'foo', - updated_at: '2021-07-08T14:35:50Z', - promoted: ['1', '2'], - }, - ], - meta: { - page: { - current: 1, - size: 10, - total_results: 2, - }, - }, - }; - - const mockActions = { - allowIgnoredQuery: jest.fn(), - loadIgnoredQueries: jest.fn(), - onPaginate: jest.fn(), - }; - - beforeAll(() => { - setMockValues(values); - setMockActions(mockActions); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const getColumn = (index: number) => { - const wrapper = shallow(); - const table = wrapper.find(EuiBasicTable); - const columns = table.prop('columns'); - return columns[index]; - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiBasicTable).exists()).toBe(true); - }); - - it('show a query', () => { - const column = getColumn(0).render('test query'); - expect(column).toEqual('test query'); - }); - - it('has an allow action', () => { - const column = getColumn(1); - // @ts-ignore - const actions = column.actions; - actions[0].onClick('test query'); - expect(mockActions.allowIgnoredQuery).toHaveBeenCalledWith('test query'); - }); - - it('fetches data on load', () => { - shallow(); - - expect(mockActions.loadIgnoredQueries).toHaveBeenCalled(); - }); - - it('supports pagination', () => { - const wrapper = shallow(); - wrapper.find(EuiBasicTable).simulate('change', { page: { index: 0 } }); - - expect(mockActions.onPaginate).toHaveBeenCalledWith(1); - }); - - it('fetches data on refresh button press', () => { - const wrapper = shallow(); - expect(mockActions.loadIgnoredQueries).toHaveBeenCalledTimes(1); - - shallow(wrapper.find(DataPanel).prop('title')).find(EuiButtonEmpty).simulate('click'); - - expect(mockActions.loadIgnoredQueries).toHaveBeenCalledTimes(2); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_panel.tsx deleted file mode 100644 index b401c54e43e40..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_panel.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { - convertMetaToPagination, - handlePageChange, -} from '../../../../../../../shared/table_pagination'; - -import { DataPanel } from '../../../../../data_panel'; - -import { IgnoredQueriesLogic } from './ignored_queries_logic'; - -export const IgnoredQueriesPanel: React.FC = () => { - const { dataLoading, ignoredQueries, meta } = useValues(IgnoredQueriesLogic); - const { allowIgnoredQuery, loadIgnoredQueries, onPaginate } = useActions(IgnoredQueriesLogic); - - useEffect(() => { - loadIgnoredQueries(); - }, [meta.page.current]); - - // @ts-expect-error - EuiBasicTable wants an array of objects, but will accept strings if coerced - const columns: Array> = [ - { - render: (query: string) => query, - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.queryColumnName', - { - defaultMessage: 'Query', - } - ), - }, - { - actions: [ - { - type: 'button', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonLabel', - { - defaultMessage: 'Allow', - } - ), - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestions.allowButtonDescription', - { - defaultMessage: 'Enable suggestions for this query', - } - ), - onClick: (query) => allowIgnoredQuery(query), - color: 'primary', - }, - ], - }, - ]; - - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.title', - { - defaultMessage: 'Ignored queries', - } - )} - - - loadIgnoredQueries()}> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.refresh', - { - defaultMessage: 'Refresh', - } - )} - - - - } - subtitle={ - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.ignoredSuggestionsPanel.description', - { - defaultMessage: 'You won’t be notified about suggestions for these queries', - } - )} - - } - iconType="eyeClosed" - hasBorder - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/index.ts deleted file mode 100644 index f4cb73919f42f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { IgnoredQueriesPanel } from './ignored_queries_panel'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts deleted file mode 100644 index 2f2c2aeb63503..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { AutomatedCurationsHistoryPanel } from './automated_curations_history_panel'; -export { IgnoredQueriesPanel } from './ignored_queries_panel'; -export { RejectedCurationsHistoryPanel } from './rejected_curations_history_panel'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx deleted file mode 100644 index 58bf89a36d5ee..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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. - */ - -// TODO use engine_logic.mock instead of setMockValues({ engineName: 'some-engine' }), currently that breaks the test -import { setMockValues } from '../../../../../../__mocks__/kea_logic'; -// import '../../../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EntSearchLogStream } from '../../../../../../shared/log_stream'; -import { DataPanel } from '../../../../data_panel'; - -import { RejectedCurationsHistoryPanel } from './rejected_curations_history_panel'; - -describe('RejectedCurationsHistoryPanel', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues({ engineName: 'some-engine' }); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(DataPanel)).toBe(true); - expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual( - 'event.kind: event and event.dataset: search-relevance-suggestions and appsearch.adaptive_relevance.engine: some-engine and event.action: curation_suggestion and appsearch.adaptive_relevance.suggestion.new_status: rejected' - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx deleted file mode 100644 index fd1255c2f48db..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/rejected_curations_history_panel.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID } from '../../../../../../../../common/constants'; -import { EntSearchLogStream } from '../../../../../../shared/log_stream'; -import { DataPanel } from '../../../../data_panel'; -import { EngineLogic } from '../../../../engine'; - -export const RejectedCurationsHistoryPanel: React.FC = () => { - const { engineName } = useValues(EngineLogic); - const [endTimestamp, setEndTimestamp] = useState(Date.now()); - - const filters = [ - 'event.kind: event', - 'event.dataset: search-relevance-suggestions', - `appsearch.adaptive_relevance.engine: ${engineName}`, - 'event.action: curation_suggestion', - 'appsearch.adaptive_relevance.suggestion.new_status: rejected', - ]; - - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableTitle', - { - defaultMessage: 'Recently rejected suggestions', - } - )} - - - setEndTimestamp(Date.now())} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engines.curations.rejectedCurationsHistoryPanel.refresh', - { - defaultMessage: 'Refresh', - } - )} - - - - } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.curations.rejectedCurationsHistoryPanel.tableDescription', - { - defaultMessage: 'View suggestions you’ve previously rejected.', - } - )} - hasBorder - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx deleted file mode 100644 index c5dc20024b62f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { - AutomatedCurationsHistoryPanel, - IgnoredQueriesPanel, - RejectedCurationsHistoryPanel, -} from './components'; -import { CurationsHistory } from './curations_history'; - -describe('CurationsHistory', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(AutomatedCurationsHistoryPanel)).toHaveLength(1); - expect(wrapper.find(RejectedCurationsHistoryPanel)).toHaveLength(1); - expect(wrapper.find(IgnoredQueriesPanel)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx deleted file mode 100644 index 41250d8f67dfd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/curations_history.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { - AutomatedCurationsHistoryPanel, - IgnoredQueriesPanel, - RejectedCurationsHistoryPanel, -} from './components'; - -export const CurationsHistory: React.FC = () => { - return ( - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/index.ts deleted file mode 100644 index bddc156f7920e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { CurationsHistory } from './curations_history'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx deleted file mode 100644 index ba8ec782a4afd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/react_router'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { set } from '@kbn/safer-lodash-set/fp'; - -import { CurationsTable, EmptyState } from '../components'; - -import { SuggestionsTable } from '../components/suggestions_table'; - -import { CurationsOverview } from './curations_overview'; - -const MOCK_VALUES = { - // CurationsSettingsLogic - curationsSettings: { - enabled: true, - }, - // CurationsLogic - curations: [ - { - id: 'cur-id-1', - }, - { - id: 'cur-id-2', - }, - ], - // EngineLogic - engine: { - adaptive_relevance_suggestions_active: true, - }, -}; - -describe('CurationsOverview', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - }); - - it('renders an empty message when there are no curations', () => { - setMockValues({ ...MOCK_VALUES, curations: [] }); - const wrapper = shallow(); - - expect(wrapper.find(EmptyState).exists()).toBe(true); - }); - - it('renders a curations table when there are curations present', () => { - setMockValues({ - ...MOCK_VALUES, - curations: [ - { - id: 'cur-id-1', - }, - { - id: 'cur-id-2', - }, - ], - }); - const wrapper = shallow(); - - expect(wrapper.find(CurationsTable)).toHaveLength(1); - }); - - it('renders a suggestions table when suggestions are active', () => { - setMockValues(set('engine.adaptive_relevance_suggestions_active', true, MOCK_VALUES)); - const wrapper = shallow(); - - expect(wrapper.find(SuggestionsTable).exists()).toBe(true); - }); - - it('doesn\t render a suggestions table when suggestions are not active', () => { - setMockValues(set('engine.adaptive_relevance_suggestions_active', false, MOCK_VALUES)); - const wrapper = shallow(); - - expect(wrapper.find(SuggestionsTable).exists()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx deleted file mode 100644 index f763c297ea1de..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_overview.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiSpacer } from '@elastic/eui'; - -import { EngineLogic } from '../../engine'; -import { CurationsTable, EmptyState } from '../components'; -import { SuggestionsTable } from '../components/suggestions_table'; -import { CurationsLogic } from '../curations_logic'; - -export const CurationsOverview: React.FC = () => { - const { curations } = useValues(CurationsLogic); - const { - engine: { adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive }, - } = useValues(EngineLogic); - - const shouldShowSuggestions = adaptiveRelevanceSuggestionsActive; - - return ( - <> - {shouldShowSuggestions && ( - <> - - - - )} - {curations.length > 0 ? : } - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx deleted file mode 100644 index 0c3e9483e9233..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 '../../../../../__mocks__/shallow_useeffect.mock'; -import '../../../../../__mocks__/react_router'; -import '../../../../__mocks__/engine_logic.mock'; - -import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButtonEmpty, EuiCallOut, EuiSwitch } from '@elastic/eui'; - -import { docLinks } from '../../../../../shared/doc_links'; - -import { Loading } from '../../../../../shared/loading'; -import { EuiButtonTo } from '../../../../../shared/react_router_helpers'; -import { DataPanel } from '../../../data_panel'; -import { LogRetentionOptions } from '../../../log_retention'; - -import { CurationsSettings } from '.'; - -const MOCK_VALUES = { - // CurationsSettingsLogic - dataLoading: false, - curationsSettings: { - enabled: true, - mode: 'automatic', - }, - // LicensingLogic - hasPlatinumLicense: true, - // EngineLogic - engine: { - analytics_enabled: true, - }, -}; - -const MOCK_ACTIONS = { - // CurationsSettingsLogic - loadCurationsSettings: jest.fn(), - onSkipLoadingCurationsSettings: jest.fn(), - toggleCurationsEnabled: jest.fn(), - toggleCurationsMode: jest.fn(), -}; - -describe('CurationsSettings', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - }); - - it('contains a switch to toggle curations settings', () => { - let wrapper: ShallowWrapper; - - setMockValues({ - ...MOCK_VALUES, - curationsSettings: { ...MOCK_VALUES.curationsSettings, enabled: true }, - }); - wrapper = shallow(); - - expect(wrapper.find(EuiSwitch).at(0).prop('checked')).toBe(true); - - setMockValues({ - ...MOCK_VALUES, - curationsSettings: { ...MOCK_VALUES.curationsSettings, enabled: false }, - }); - wrapper = shallow(); - - expect(wrapper.find(EuiSwitch).at(0).prop('checked')).toBe(false); - - wrapper.find(EuiSwitch).at(0).simulate('change'); - expect(MOCK_ACTIONS.toggleCurationsEnabled).toHaveBeenCalled(); - }); - - it('contains a switch to toggle the curations mode', () => { - let wrapper: ShallowWrapper; - - setMockValues({ - ...MOCK_VALUES, - curationsSettings: { ...MOCK_VALUES.curationsSettings, mode: 'automatic' }, - }); - wrapper = shallow(); - - expect(wrapper.find(EuiSwitch).at(1).prop('checked')).toBe(true); - - setMockValues({ - ...MOCK_VALUES, - curationsSettings: { ...MOCK_VALUES.curationsSettings, mode: 'manual' }, - }); - wrapper = shallow(); - - expect(wrapper.find(EuiSwitch).at(1).prop('checked')).toBe(false); - - wrapper.find(EuiSwitch).at(1).simulate('change'); - expect(MOCK_ACTIONS.toggleCurationsMode).toHaveBeenCalled(); - }); - - it('enables form elements and hides the callout when analytics retention is enabled', () => { - setMockValues({ - ...MOCK_VALUES, - logRetention: { - [LogRetentionOptions.Analytics]: { - enabled: true, - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSwitch).at(0).prop('disabled')).toBe(false); - expect(wrapper.find(EuiSwitch).at(1).prop('disabled')).toBe(false); - expect(wrapper.find(EuiCallOut)).toHaveLength(0); - }); - - it('display a callout and disables form elements when analytics retention is disabled', () => { - setMockValues({ - ...MOCK_VALUES, - engine: { - analytics_enabled: false, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiSwitch).at(0).prop('disabled')).toBe(true); - expect(wrapper.find(EuiSwitch).at(1).prop('disabled')).toBe(true); - expect(wrapper.find(EuiCallOut).dive().find(EuiButtonTo).prop('to')).toEqual('/settings'); - }); - - it('returns a loading state when curations data is loading', () => { - setMockValues({ - ...MOCK_VALUES, - dataLoading: true, - }); - const wrapper = shallow(); - - expect(wrapper.is(Loading)).toBe(true); - }); - - describe('loading curation settings based on analytics logs availability', () => { - it('loads curation settings when analytics logs are enabled', () => { - shallow(); - - expect(MOCK_ACTIONS.loadCurationsSettings).toHaveBeenCalledTimes(1); - }); - - it('skips loading curation settings when analytics logs are disabled', () => { - setMockValues({ - ...MOCK_VALUES, - engine: { - analytics_enabled: false, - }, - }); - - shallow(); - - expect(MOCK_ACTIONS.loadCurationsSettings).toHaveBeenCalledTimes(0); - expect(MOCK_ACTIONS.onSkipLoadingCurationsSettings).toHaveBeenCalledTimes(1); - }); - }); - - describe('when the user has no platinum license', () => { - beforeEach(() => { - setMockValues({ - ...MOCK_VALUES, - hasPlatinumLicense: false, - }); - }); - - it('shows a CTA to upgrade your license when the user when the user', () => { - const wrapper = shallow(); - expect(wrapper.is(DataPanel)).toBe(true); - expect(wrapper.prop('action').props.to).toEqual('/app/management/stack/license_management'); - expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual(docLinks.licenseManagement); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx deleted file mode 100644 index 4b7d8a777c2bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiSwitch, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../../../shared/doc_links'; -import { LicensingLogic } from '../../../../../shared/licensing'; -import { Loading } from '../../../../../shared/loading'; -import { EuiButtonTo } from '../../../../../shared/react_router_helpers'; -import { SETTINGS_PATH } from '../../../../routes'; -import { DataPanel } from '../../../data_panel'; - -import { EngineLogic } from '../../../engine'; - -import { AutomatedIcon } from '../../components/automated_icon'; - -import { CurationsSettingsLogic } from './curations_settings_logic'; - -export const CurationsSettings: React.FC = () => { - const { hasPlatinumLicense } = useValues(LicensingLogic); - - const { - curationsSettings: { enabled, mode }, - dataLoading, - } = useValues(CurationsSettingsLogic); - const { - loadCurationsSettings, - onSkipLoadingCurationsSettings, - toggleCurationsEnabled, - toggleCurationsMode, - } = useActions(CurationsSettingsLogic); - - const { - engine: { analytics_enabled: analyticsEnabled }, - } = useValues(EngineLogic); - - useEffect(() => { - if (analyticsEnabled && dataLoading) { - loadCurationsSettings(); - } else { - onSkipLoadingCurationsSettings(); - } - }, [analyticsEnabled, dataLoading]); - - if (!hasPlatinumLicense) - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTATitle', - { - defaultMessage: 'Introducing curations powered by adaptive relevance', - } - )} - - } - subtitle={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.curations.settings.platinum', { - defaultMessage: 'Platinum', - })} - - ), - }} - /> - } - action={ - - {i18n.translate( - 'xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel', - { - defaultMessage: 'Start a 30-day trial', - } - )} - - } - > - - {i18n.translate('xpack.enterpriseSearch.curations.settings.licenseUpgradeLink', { - defaultMessage: 'Learn more about license upgrades', - })} - - - ); - if (dataLoading) return ; - - return ( - <> - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsTitle', - { - defaultMessage: 'Curations powered by adaptive relevance', - } - )} -

-
-
-
- - {!analyticsEnabled && ( - <> - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutDescription', - { - defaultMessage: - 'Adaptive relevance requires analytics to be enabled on your account.', - } - )} -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.settings.manageAnalyticsButtonLabel', - { defaultMessage: 'Manage analytics' } - )} - -
- - - )} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsDescription', - { - defaultMessage: - "App Search will monitor your engine's analytics and suggest changes to your curations to help you deliver the most relevant results. Each suggestion can be accepted, rejected, or modified.", - } - )} - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts deleted file mode 100644 index e3f8a47b401b4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/engine_logic.mock'; - -jest.mock('../../curations_logic', () => ({ - CurationsLogic: { - values: {}, - actions: { - loadCurations: jest.fn(), - }, - }, -})); - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { CurationsLogic } from '../..'; -import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; -import { EngineLogic } from '../../../engine'; - -import { CurationsSettingsLogic } from './curations_settings_logic'; - -const DEFAULT_VALUES = { - dataLoading: true, - curationsSettings: { - enabled: false, - mode: 'manual', - }, -}; - -describe('CurationsSettingsLogic', () => { - const { mount } = new LogicMounter(CurationsSettingsLogic); - const { http } = mockHttpValues; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has correct default values', () => { - mount(); - expect(CurationsSettingsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onCurationsSettingsLoad', () => { - it('saves curation settings and that data has loaded', () => { - mount(); - - CurationsSettingsLogic.actions.onCurationsSettingsLoad({ - enabled: true, - mode: 'automatic', - }); - - expect(CurationsSettingsLogic.values.dataLoading).toEqual(false); - expect(CurationsSettingsLogic.values.curationsSettings).toEqual({ - enabled: true, - mode: 'automatic', - }); - }); - }); - - describe('onSkipCurationsSettingsLoad', () => { - it('saves that data has loaded', () => { - mount(); - - CurationsSettingsLogic.actions.onSkipLoadingCurationsSettings(); - - expect(CurationsSettingsLogic.values.dataLoading).toEqual(false); - }); - }); - }); - - describe('listeners', () => { - describe('loadCurationsSettings', () => { - it('calls the curations settings API and saves the returned settings', async () => { - http.get.mockReturnValueOnce( - Promise.resolve({ - curation: { - enabled: true, - mode: 'automatic', - }, - }) - ); - mount(); - jest.spyOn(CurationsSettingsLogic.actions, 'onCurationsSettingsLoad'); - - CurationsSettingsLogic.actions.loadCurationsSettings(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/settings' - ); - expect(CurationsSettingsLogic.actions.onCurationsSettingsLoad).toHaveBeenCalledWith({ - enabled: true, - mode: 'automatic', - }); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - CurationsSettingsLogic.actions.loadCurationsSettings(); - }); - }); - - describe('toggleCurationsEnabled', () => { - it('enables curations when they are currently disabled', () => { - mount({ - curationsSettings: { - ...DEFAULT_VALUES.curationsSettings, - enabled: false, - }, - }); - jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting'); - - CurationsSettingsLogic.actions.toggleCurationsEnabled(); - - expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({ - enabled: true, - }); - }); - - it('disables curations when they are currently enabled', () => { - mount({ - curationsSettings: { - ...DEFAULT_VALUES.curationsSettings, - enabled: true, - }, - }); - jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting'); - - CurationsSettingsLogic.actions.toggleCurationsEnabled(); - - expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({ - enabled: false, - mode: 'manual', - }); - }); - }); - - describe('toggleCurationsMode', () => { - it('sets to manual mode when it is currently automatic', () => { - mount({ - curationsSettings: { - ...DEFAULT_VALUES.curationsSettings, - mode: 'automatic', - }, - }); - jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting'); - - CurationsSettingsLogic.actions.toggleCurationsMode(); - - expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({ - mode: 'manual', - }); - }); - - it('sets to automatic mode when it is currently manual', () => { - mount({ - curationsSettings: { - ...DEFAULT_VALUES.curationsSettings, - mode: 'manual', - }, - }); - jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting'); - - CurationsSettingsLogic.actions.toggleCurationsMode(); - - expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({ - mode: 'automatic', - }); - }); - }); - - describe('updateCurationsSetting', () => { - it('calls the curations settings API and saves the returned settings', async () => { - http.put.mockReturnValueOnce( - Promise.resolve({ - curation: { - enabled: true, - mode: 'automatic', - }, - }) - ); - mount(); - jest.spyOn(CurationsSettingsLogic.actions, 'onCurationsSettingsLoad'); - - CurationsSettingsLogic.actions.updateCurationsSetting({ - enabled: true, - }); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/adaptive_relevance/settings', - { - body: JSON.stringify({ - curation: { - enabled: true, - }, - }), - } - ); - expect(CurationsSettingsLogic.actions.onCurationsSettingsLoad).toHaveBeenCalledWith({ - enabled: true, - mode: 'automatic', - }); - - // data should have been reloaded - expect(EngineLogic.actions.initializeEngine).toHaveBeenCalled(); - expect(CurationsLogic.actions.loadCurations).toHaveBeenCalled(); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - CurationsSettingsLogic.actions.updateCurationsSetting({}); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts deleted file mode 100644 index f9755000497b2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../../shared/http'; -import { EngineLogic } from '../../../engine'; -import { CurationsLogic } from '../../curations_logic'; - -export interface CurationsSettings { - enabled: boolean; - mode: 'automatic' | 'manual'; -} - -interface CurationsSettingsValues { - dataLoading: boolean; - curationsSettings: CurationsSettings; -} - -interface CurationsSettingsActions { - loadCurationsSettings(): void; - onCurationsSettingsLoad(curationsSettings: CurationsSettings): { - curationsSettings: CurationsSettings; - }; - onSkipLoadingCurationsSettings(): void; - toggleCurationsEnabled(): void; - toggleCurationsMode(): void; - updateCurationsSetting(currationsSetting: Partial): { - currationsSetting: Partial; - }; -} - -export const CurationsSettingsLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'curations', 'curations_settings_logic'], - actions: () => ({ - loadCurationsSettings: true, - onCurationsSettingsLoad: (curationsSettings) => ({ curationsSettings }), - onSkipLoadingCurationsSettings: true, - toggleCurationsEnabled: true, - toggleCurationsMode: true, - updateCurationsSetting: (currationsSetting) => ({ currationsSetting }), - }), - reducers: () => ({ - dataLoading: [ - true, - { - onCurationsSettingsLoad: () => false, - onSkipLoadingCurationsSettings: () => false, - }, - ], - curationsSettings: [ - { - enabled: false, - mode: 'manual', - }, - { - // @ts-expect-error upgrade typescript v5.1.6 - onCurationsSettingsLoad: (_, { curationsSettings }) => curationsSettings, - }, - ], - }), - listeners: ({ actions, values }) => ({ - loadCurationsSettings: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get<{ curation: CurationsSettings }>( - `/internal/app_search/engines/${engineName}/adaptive_relevance/settings` - ); - actions.onCurationsSettingsLoad(response.curation); - } catch (e) { - flashAPIErrors(e); - } - }, - toggleCurationsEnabled: async () => { - if (values.curationsSettings.enabled) { - actions.updateCurationsSetting({ enabled: false, mode: 'manual' }); - } else { - actions.updateCurationsSetting({ enabled: true }); - } - }, - toggleCurationsMode: async () => { - actions.updateCurationsSetting({ - mode: values.curationsSettings.mode === 'automatic' ? 'manual' : 'automatic', - }); - }, - updateCurationsSetting: async ({ currationsSetting }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - try { - const response = await http.put<{ curation: CurationsSettings }>( - `/internal/app_search/engines/${engineName}/adaptive_relevance/settings`, - { - body: JSON.stringify({ curation: currationsSetting }), - } - ); - actions.onCurationsSettingsLoad(response.curation); - - // Re-fetch data so that UI updates to new settings - CurationsLogic.actions.loadCurations(); - EngineLogic.actions.initializeEngine(); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/index.ts deleted file mode 100644 index fd7d3156cc5ab..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { CurationsSettings } from './curations_settings'; -export { CurationsSettingsLogic } from './curations_settings_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/index.ts deleted file mode 100644 index 7268c0fdbc4dc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { Curations } from './curations'; -export { CurationCreation } from './curation_creation'; -export { CurationSuggestion } from './curation_suggestion'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/index.ts deleted file mode 100644 index e483e28dcc2e8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/data_panel/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { DataPanel } from '../../../shared/data_panel/data_panel'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx deleted file mode 100644 index bd4dbfe8fa061..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const FLYOUT_ARIA_LABEL_ID = 'documentCreationFlyoutHeadingId'; - -export const DOCUMENT_CREATION_ERRORS = { - TITLE: i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.errorsTitle', { - defaultMessage: 'Something went wrong. Please address the errors and try again.', - }), - NO_FILE: i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.noFileFound', { - defaultMessage: 'No file found.', - }), - NO_VALID_FILE: i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.noValidFile', { - defaultMessage: 'Problem parsing file.', - }), - NOT_VALID: i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.notValidJson', { - defaultMessage: 'Document contents must be a valid JSON array or object.', - }), -}; -export const DOCUMENT_CREATION_WARNINGS = { - TITLE: i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.warningsTitle', { - defaultMessage: 'Warning!', - }), - LARGE_FILE: i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.largeFile', { - defaultMessage: - "You're uploading an extremely large file. This could potentially lock your browser, or take a very long time to process. If possible, try splitting your data up into multiple smaller files.", - }), -}; - -// This is indented the way it is to work with ApiCodeExample. -// Use dedent() when calling this alone -export const DOCUMENTS_API_JSON_EXAMPLE = `[ - { - "id": "park_rocky-mountain", - "title": "Rocky Mountain", - "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).", - "nps_link": "https://www.nps.gov/romo/index.htm", - "states": [ - "Colorado" - ], - "visitors": 4517585, - "world_heritage_site": false, - "location": "40.4,-105.58", - "acres": 265795.2, - "square_km": 1075.6, - "date_established": "1915-01-26T06:00:00Z" - }, - { - "id": "park_saguaro", - "title": "Saguaro", - "description": "Split into the separate Rincon Mountain and Tucson Mountain districts, this park is evidence that the dry Sonoran Desert is still home to a great variety of life spanning six biotic communities. Beyond the namesake giant saguaro cacti, there are barrel cacti, chollas, and prickly pears, as well as lesser long-nosed bats, spotted owls, and javelinas.", - "nps_link": "https://www.nps.gov/sagu/index.htm", - "states": [ - "Arizona" - ], - "visitors": 820426, - "world_heritage_site": false, - "location": "32.25,-110.5", - "acres": 91715.72, - "square_km": 371.2, - "date_established": "1994-10-14T05:00:00Z" - } - ]`; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx deleted file mode 100644 index 97ca9f21b0415..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 '../../../../__mocks__/enterprise_search_url.mock'; -import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiCode, EuiCodeBlock, EuiButtonEmpty } from '@elastic/eui'; - -import { ApiCodeExample, FlyoutHeader, FlyoutBody, FlyoutFooter } from './api_code_example'; - -describe('ApiCodeExample', () => { - const values = { - engineName: 'test-engine', - engine: { apiKey: 'test-key' }, - }; - const actions = { - closeDocumentCreation: jest.fn(), - }; - - beforeAll(() => { - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(FlyoutHeader)).toHaveLength(1); - expect(wrapper.find(FlyoutBody)).toHaveLength(1); - expect(wrapper.find(FlyoutFooter)).toHaveLength(1); - }); - - describe('FlyoutHeader', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('h2').text()).toEqual('Indexing by API'); - }); - }); - - describe('FlyoutBody', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - wrapper = shallow(); - }); - - it('renders with the full remote Enterprise Search API URL', () => { - expect(wrapper.find(EuiCode).dive().text()).toEqual( - 'http://localhost:3002/api/as/v1/engines/test-engine/documents' - ); - expect(wrapper.find(EuiCodeBlock).dive().text()).toEqual( - expect.stringContaining('http://localhost:3002/api/as/v1/engines/test-engine/documents') - ); - }); - - it('renders with the API key', () => { - expect(wrapper.find(EuiCodeBlock).dive().text()).toEqual(expect.stringContaining('test-key')); - }); - }); - - describe('FlyoutFooter', () => { - it('closes the flyout', () => { - const wrapper = shallow(); - - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(actions.closeDocumentCreation).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx deleted file mode 100644 index 98da6dc88ef57..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiButtonEmpty, - EuiText, - EuiLink, - EuiSpacer, - EuiPanel, - EuiBadge, - EuiCode, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { DocumentCreationLogic } from '..'; -import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; -import { docLinks } from '../../../../shared/doc_links'; -import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; -import { EngineLogic } from '../../engine'; -import { EngineDetails } from '../../engine/types'; - -import { DOCUMENTS_API_JSON_EXAMPLE, FLYOUT_ARIA_LABEL_ID } from '../constants'; - -export const ApiCodeExample: React.FC = () => ( - <> - - - - -); - -export const FlyoutHeader: React.FC = () => { - return ( - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.title', { - defaultMessage: 'Indexing by API', - })} -

-
-
- ); -}; - -export const FlyoutBody: React.FC = () => { - const { engineName, engine } = useValues(EngineLogic); - const { apiKey } = engine as EngineDetails; - - const documentsApiUrl = getEnterpriseSearchUrl(`/api/as/v1/engines/${engineName}/documents`); - - return ( - - -

- - documents API - - ), - clientLibrariesLink: ( - - client libraries - - ), - }} - /> -

-

- {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.example', { - defaultMessage: - 'To see the API in action, you can experiment with the example request below using a command line or a client library.', - })} -

-
- - - - - POST - - - {documentsApiUrl} - - - - - {`\ -curl -X POST '${documentsApiUrl}' \\ - -H 'Content-Type: application/json' \\ - -H 'Authorization: Bearer ${apiKey}' \\ - -d '${DOCUMENTS_API_JSON_EXAMPLE}' -# Returns -# [ -# { -# "id": "park_rocky-mountain", -# "errors": [] -# }, -# { -# "id": "park_saguaro", -# "errors": [] -# } -# ]`} - -
- ); -}; - -export const FlyoutFooter: React.FC = () => { - const { closeDocumentCreation } = useActions(DocumentCreationLogic); - - return ( - - {CANCEL_BUTTON_LABEL} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/elasticsearch_index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/elasticsearch_index.tsx deleted file mode 100644 index b7dce0f996a6c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/elasticsearch_index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlyoutHeader, - EuiLink, - EuiTitle, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiButtonEmpty, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { DocumentCreationLogic } from '..'; -import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; - -import { FLYOUT_ARIA_LABEL_ID } from '../constants'; -import { Errors } from '../creation_response_components'; - -import './paste_json_text.scss'; - -export const ElasticsearchIndex: React.FC = () => ( - <> - - - - -); - -export const FlyoutHeader: React.FC = () => { - return ( - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.title', - { - defaultMessage: 'Connect an Elasticsearch index', - } - )} -

-
-
- ); -}; - -export const FlyoutBody: React.FC = () => { - return ( - }> - -

- - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.link', - { - defaultMessage: 'Learn more about using an existing index', - } - )} - - ), - }} - /> -

-
- - {'{Form fields go here}'} -
- ); -}; - -export const FlyoutFooter: React.FC = () => { - // TODO: replace these - const { textInput, isUploading } = useValues(DocumentCreationLogic); - // TODO: replace 'onSubmitJson' - const { onSubmitJson, closeDocumentCreation } = useActions(DocumentCreationLogic); - - return ( - - - - {CANCEL_BUTTON_LABEL} - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.button', - { - defaultMessage: 'Connect to index', - } - )} - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts deleted file mode 100644 index 05ccb398c68f9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 { ShowCreationModes } from './show_creation_modes'; -export { ApiCodeExample } from './api_code_example'; -export { JsonFlyout } from './json_flyout'; -export { ElasticsearchIndex } from './elasticsearch_index'; -export { PasteJsonTextTabContent, PasteJsonTextFooterContent } from './paste_json_text'; -export { UploadJsonFileTabContent, UploadJsonFileFooterContent } from './upload_json_file'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.scss deleted file mode 100644 index 7c84a2868e889..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.scss +++ /dev/null @@ -1,3 +0,0 @@ -.enterpriseSearchTabbedFlyoutHeader.euiFlyoutHeader { - padding-bottom: 0; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.test.tsx deleted file mode 100644 index d70cd260d573f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyoutHeader, EuiFlyoutBody, EuiFlyoutFooter, EuiTabs, EuiTab } from '@elastic/eui'; - -import { - JsonFlyout, - PasteJsonTextTabContent, - UploadJsonFileTabContent, - PasteJsonTextFooterContent, - UploadJsonFileFooterContent, -} from '.'; - -describe('JsonFlyout', () => { - const values = { - activeJsonTab: 'uploadTab', - }; - const actions = { - closeDocumentCreation: jest.fn(), - setActiveJsonTab: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a flyout components', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFlyoutHeader)).toHaveLength(1); - expect(wrapper.find(EuiFlyoutBody)).toHaveLength(1); - expect(wrapper.find(EuiFlyoutFooter)).toHaveLength(1); - }); - - it('renders Upload json components and calls method with correct param', () => { - const wrapper = shallow(); - const tabs = wrapper.find(EuiTabs).find(EuiTab); - - expect(tabs).toHaveLength(2); - - tabs.at(1).simulate('click'); - - expect(actions.setActiveJsonTab).toHaveBeenCalledWith('pasteTab'); - expect(wrapper.find(UploadJsonFileTabContent)).toHaveLength(1); - expect(wrapper.find(UploadJsonFileFooterContent)).toHaveLength(1); - }); - - it('renders Paste json components and calls method with correct param', () => { - setMockValues({ ...values, activeJsonTab: 'pasteTab' }); - const wrapper = shallow(); - const tabs = wrapper.find(EuiTabs).find(EuiTab); - - expect(tabs).toHaveLength(2); - - tabs.at(0).simulate('click'); - - expect(actions.setActiveJsonTab).toHaveBeenCalledWith('uploadTab'); - expect(wrapper.find(PasteJsonTextTabContent)).toHaveLength(1); - expect(wrapper.find(PasteJsonTextFooterContent)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.tsx deleted file mode 100644 index f9ed45bf38996..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/json_flyout.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiSpacer, - EuiTabs, - EuiTab, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic, ActiveJsonTab } from '..'; -import { FLYOUT_ARIA_LABEL_ID } from '../constants'; -import { Errors } from '../creation_response_components'; - -import { - PasteJsonTextTabContent, - UploadJsonFileTabContent, - PasteJsonTextFooterContent, - UploadJsonFileFooterContent, -} from '.'; - -import './json_flyout.scss'; - -export const JsonFlyout: React.FC = () => { - const { activeJsonTab } = useValues(DocumentCreationLogic); - const { setActiveJsonTab } = useActions(DocumentCreationLogic); - - const tabs = [ - { - id: 'uploadTab', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.uploadTabName', - { - defaultMessage: 'Upload', - } - ), - content: , - }, - { - id: 'pasteTab', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.pasteTabName', - { - defaultMessage: 'Paste', - } - ), - content: , - }, - ]; - - return ( - <> - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.title', { - defaultMessage: 'Paste or upload JSON', - })} -

-
- - - {tabs.map((tab, index) => ( - setActiveJsonTab(tab.id as ActiveJsonTab)} - isSelected={tab.id === activeJsonTab} - > - {tab.name} - - ))} - -
- }> - {tabs.find((tab) => tab.id === activeJsonTab)?.content} - - - {activeJsonTab === 'uploadTab' ? ( - - ) : ( - - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss deleted file mode 100644 index 65bae70a1f939..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss +++ /dev/null @@ -1,3 +0,0 @@ -.pasteJsonTextArea { - font-family: $euiCodeFontFamily; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx deleted file mode 100644 index 492b9593e5c8e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiTextArea, EuiButtonEmpty, EuiButton } from '@elastic/eui'; - -import { rerender } from '../../../../test_helpers'; - -import { PasteJsonTextTabContent, PasteJsonTextFooterContent } from './paste_json_text'; - -describe('PasteJsonText', () => { - const values = { - textInput: 'hello world', - isUploading: false, - errors: [], - configuredLimits: { - engine: { - maxDocumentByteSize: 102400, - }, - }, - }; - const actions = { - setTextInput: jest.fn(), - onSubmitJson: jest.fn(), - closeDocumentCreation: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - describe('PasteJsonTextTabContent', () => { - it('renders and updates the textarea value', () => { - setMockValues({ ...values, textInput: 'lorem ipsum' }); - const wrapper = shallow(); - const textarea = wrapper.find(EuiTextArea); - - expect(textarea.prop('value')).toEqual('lorem ipsum'); - - textarea.simulate('change', { target: { value: 'dolor sit amet' } }); - expect(actions.setTextInput).toHaveBeenCalledWith('dolor sit amet'); - }); - }); - - describe('PasteJsonTextFooterContent', () => { - it('closes the modal', () => { - const wrapper = shallow(); - - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(actions.closeDocumentCreation).toHaveBeenCalled(); - }); - - it('submits json', () => { - const wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - expect(actions.onSubmitJson).toHaveBeenCalled(); - }); - - it('disables/enables the Continue button based on whether text has been entered', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(false); - - setMockValues({ ...values, textInput: '' }); - rerender(wrapper); - expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); - }); - - it('sets isLoading based on isUploading', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButton).prop('isLoading')).toBe(false); - - setMockValues({ ...values, isUploading: true }); - rerender(wrapper); - expect(wrapper.find(EuiButton).prop('isLoading')).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx deleted file mode 100644 index 2fd7aac12a5e5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlexItem, - EuiFlexGroup, - EuiButton, - EuiButtonEmpty, - EuiTextArea, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic } from '..'; -import { CANCEL_BUTTON_LABEL, CONTINUE_BUTTON_LABEL } from '../../../../shared/constants'; -import { AppLogic } from '../../../app_logic'; - -import './paste_json_text.scss'; - -export const PasteJsonTextTabContent: React.FC = () => { - const { - configuredLimits: { - engine: { maxDocumentByteSize }, - }, - } = useValues(AppLogic); - - const { textInput, errors } = useValues(DocumentCreationLogic); - const { setTextInput } = useActions(DocumentCreationLogic); - - return ( - <> - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.description', - { - defaultMessage: - 'Paste an array of JSON documents. Ensure the JSON is valid and that each document object is less than {maxDocumentByteSize} bytes.', - values: { maxDocumentByteSize }, - } - )} -

-
- - setTextInput(e.target.value)} - isInvalid={errors.length > 0} - aria-label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.label', - { defaultMessage: 'Paste JSON here' } - )} - className="pasteJsonTextArea" - fullWidth - rows={12} - /> - - ); -}; - -export const PasteJsonTextFooterContent: React.FC = () => { - const { textInput, isUploading } = useValues(DocumentCreationLogic); - const { onSubmitJson, closeDocumentCreation } = useActions(DocumentCreationLogic); - - return ( - - - {CANCEL_BUTTON_LABEL} - - - - {CONTINUE_BUTTON_LABEL} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx deleted file mode 100644 index 343d2ca4470d3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButtonEmpty } from '@elastic/eui'; - -import { DocumentCreationButtons } from '..'; - -import { ShowCreationModes } from '.'; - -describe('ShowCreationModes', () => { - const actions = { - closeDocumentCreation: jest.fn(), - }; - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockActions(actions); - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.find('h2').text()).toEqual('Add new documents'); - expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); - }); - - it('closes the flyout', () => { - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(actions.closeDocumentCreation).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx deleted file mode 100644 index 7380e085a5e3c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 } from 'kea'; - -import { - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiButtonEmpty, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic, DocumentCreationButtons } from '..'; -import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; -import { FLYOUT_ARIA_LABEL_ID } from '../constants'; - -export const ShowCreationModes: React.FC = () => { - const { closeDocumentCreation } = useActions(DocumentCreationLogic); - - return ( - <> - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title', - { defaultMessage: 'Add new documents' } - )} -

-
-
- - - - - {CANCEL_BUTTON_LABEL} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx deleted file mode 100644 index 38689279c8fce..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFilePicker, EuiButtonEmpty, EuiButton } from '@elastic/eui'; - -import { rerender } from '../../../../test_helpers'; - -import { UploadJsonFileTabContent, UploadJsonFileFooterContent } from './upload_json_file'; - -describe('UploadJsonFile', () => { - const mockFile = new File(['mock'], 'mock.json', { type: 'application/json' }); - const values = { - fileInput: null, - isUploading: false, - errors: [], - configuredLimits: { - engine: { - maxDocumentByteSize: 102400, - }, - }, - }; - const actions = { - setFileInput: jest.fn(), - onSubmitFile: jest.fn(), - closeDocumentCreation: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - describe('UploadJsonFileTabContent', () => { - it('updates fileInput when files are added & removed', () => { - const wrapper = shallow(); - - wrapper.find(EuiFilePicker).simulate('change', [mockFile]); - expect(actions.setFileInput).toHaveBeenCalledWith(mockFile); - - wrapper.find(EuiFilePicker).simulate('change', []); - expect(actions.setFileInput).toHaveBeenCalledWith(null); - }); - - it('sets isLoading based on isUploading', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFilePicker).prop('isLoading')).toBe(false); - - setMockValues({ ...values, isUploading: true }); - rerender(wrapper); - expect(wrapper.find(EuiFilePicker).prop('isLoading')).toBe(true); - }); - }); - - describe('UploadJsonFileFooterContent', () => { - it('closes the flyout', () => { - const wrapper = shallow(); - - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(actions.closeDocumentCreation).toHaveBeenCalled(); - }); - - it('submits the json file', () => { - const wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - expect(actions.onSubmitFile).toHaveBeenCalled(); - }); - - it('disables/enables the Continue button based on whether files have been uploaded', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); - - setMockValues({ ...values, fineInput: mockFile }); - rerender(wrapper); - expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); - }); - - it('sets isLoading based on isUploading', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButton).prop('isLoading')).toBe(false); - - setMockValues({ ...values, isUploading: true }); - rerender(wrapper); - expect(wrapper.find(EuiButton).prop('isLoading')).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx deleted file mode 100644 index 8d9dd2419d2bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiButtonEmpty, - EuiFilePicker, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic } from '..'; -import { CANCEL_BUTTON_LABEL, CONTINUE_BUTTON_LABEL } from '../../../../shared/constants'; -import { AppLogic } from '../../../app_logic'; - -export const UploadJsonFileTabContent: React.FC = () => { - const { - configuredLimits: { - engine: { maxDocumentByteSize }, - }, - } = useValues(AppLogic); - - const { isUploading, errors } = useValues(DocumentCreationLogic); - const { setFileInput } = useActions(DocumentCreationLogic); - - return ( - <> - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.label', - { - defaultMessage: - 'If you have a .json file, drag and drop or upload it. Ensure the JSON is valid and that each document object is less than {maxDocumentByteSize} bytes.', - values: { maxDocumentByteSize }, - } - )} -

-
- - setFileInput(files?.length ? files[0] : null)} - accept="application/json" - fullWidth - isLoading={isUploading} - isInvalid={errors.length > 0} - /> - - ); -}; - -export const UploadJsonFileFooterContent: React.FC = () => { - const { fileInput, isUploading } = useValues(DocumentCreationLogic); - const { onSubmitFile, closeDocumentCreation } = useActions(DocumentCreationLogic); - - return ( - - - {CANCEL_BUTTON_LABEL} - - - - {CONTINUE_BUTTON_LABEL} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/errors.test.tsx deleted file mode 100644 index b0f7180700525..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/errors.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCallOut } from '@elastic/eui'; - -import { Errors } from '.'; - -describe('Errors', () => { - it('does not render if no errors or warnings to render', () => { - setMockValues({ errors: [], warnings: [] }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(0); - }); - - it('renders errors', () => { - setMockValues({ errors: ['error 1', 'error 2'], warnings: [] }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find(EuiCallOut).prop('title')).toEqual( - 'Something went wrong. Please address the errors and try again.' - ); - expect(wrapper.find('p').first().text()).toEqual('error 1'); - expect(wrapper.find('p').last().text()).toEqual('error 2'); - }); - - it('renders warnings', () => { - setMockValues({ errors: [], warnings: ['document size warning'] }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find(EuiCallOut).prop('title')).toEqual('Warning!'); - expect(wrapper.find('p').text()).toEqual('document size warning'); - }); - - it('renders both errors and warnings', () => { - setMockValues({ errors: ['some error'], warnings: ['some warning'] }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(2); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/errors.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/errors.tsx deleted file mode 100644 index 164b9ffefe182..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/errors.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiCallOut } from '@elastic/eui'; - -import { DocumentCreationLogic } from '..'; -import { DOCUMENT_CREATION_ERRORS, DOCUMENT_CREATION_WARNINGS } from '../constants'; - -export const Errors: React.FC = () => { - const { errors, warnings } = useValues(DocumentCreationLogic); - - return ( - <> - {errors.length > 0 && ( - - {errors.map((message, index) => ( -

{message}

- ))} -
- )} - {warnings.length > 0 && ( - - {warnings.map((message, index) => ( -

{message}

- ))} -
- )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/index.ts deleted file mode 100644 index f59f14ee66a2d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { Errors } from './errors'; -export { Summary } from './summary'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary.test.tsx deleted file mode 100644 index 28358a4149184..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyoutBody, EuiCallOut, EuiButton } from '@elastic/eui'; - -import { FlyoutHeader, FlyoutBody, FlyoutFooter } from './summary'; -import { - InvalidDocumentsSummary, - ValidDocumentsSummary, - SchemaFieldsSummary, -} from './summary_sections'; - -import { Summary } from '.'; - -describe('Summary', () => { - const values = { - summary: { - invalidDocuments: { - total: 0, - }, - }, - }; - const actions = { - setCreationStep: jest.fn(), - closeDocumentCreation: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(FlyoutHeader)).toHaveLength(1); - expect(wrapper.find(FlyoutBody)).toHaveLength(1); - expect(wrapper.find(FlyoutFooter)).toHaveLength(1); - }); - - describe('FlyoutHeader', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('h2').text()).toEqual('Indexing summary'); - }); - }); - - describe('FlyoutBody', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(InvalidDocumentsSummary)).toHaveLength(1); - expect(wrapper.find(ValidDocumentsSummary)).toHaveLength(1); - expect(wrapper.find(SchemaFieldsSummary)).toHaveLength(1); - }); - - it('shows an error callout as a flyout banner when the upload contained invalid document(s)', () => { - setMockValues({ summary: { invalidDocuments: { total: 1 } } }); - const wrapper = shallow(); - const banner = wrapper.find(EuiFlyoutBody).prop('banner') as any; - - expect(banner.type).toEqual(EuiCallOut); - expect(banner.props.color).toEqual('danger'); - expect(banner.props.iconType).toEqual('warning'); - expect(banner.props.title).toEqual( - 'Something went wrong. Please address the errors and try again.' - ); - }); - }); - - describe('FlyoutFooter', () => { - it('closes the flyout', () => { - const wrapper = shallow(); - - wrapper.find(EuiButton).simulate('click'); - expect(actions.closeDocumentCreation).toHaveBeenCalled(); - }); - - it('shows a "Fix errors" button when the upload contained invalid document(s)', () => { - setMockValues({ summary: { invalidDocuments: { total: 5 } } }); - const wrapper = shallow(); - - wrapper.find(EuiButton).last().simulate('click'); - expect(actions.setCreationStep).toHaveBeenCalledWith(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary.tsx deleted file mode 100644 index d3f487d419be0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiCallOut, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButton, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic } from '..'; -import { CLOSE_BUTTON_LABEL } from '../../../../shared/constants'; -import { FLYOUT_ARIA_LABEL_ID, DOCUMENT_CREATION_ERRORS } from '../constants'; -import { DocumentCreationStep } from '../types'; - -import { - InvalidDocumentsSummary, - ValidDocumentsSummary, - SchemaFieldsSummary, -} from './summary_sections'; - -export const Summary: React.FC = () => { - return ( - <> - - - - - ); -}; - -export const FlyoutHeader: React.FC = () => { - return ( - - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.showSummary.title', { - defaultMessage: 'Indexing summary', - })} -

-
-
- ); -}; - -export const FlyoutBody: React.FC = () => { - const { summary } = useValues(DocumentCreationLogic); - const hasInvalidDocuments = summary.invalidDocuments.total > 0; - const invalidDocumentsBanner = ( - - ); - - return ( - - - - - - ); -}; - -export const FlyoutFooter: React.FC = () => { - const { setCreationStep, closeDocumentCreation } = useActions(DocumentCreationLogic); - const { summary } = useValues(DocumentCreationLogic); - const hasInvalidDocuments = summary.invalidDocuments.total > 0; - - return ( - - - - {CLOSE_BUTTON_LABEL} - - {hasInvalidDocuments && ( - - setCreationStep(DocumentCreationStep.AddDocuments)}> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.showSummary.fixErrors', - { defaultMessage: 'Fix errors' } - )} - - - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_documents.test.tsx deleted file mode 100644 index cd8209bafed3f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_documents.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; - -import { ExampleDocumentJson, MoreDocumentsText } from './summary_documents'; - -describe('ExampleDocumentJson', () => { - const exampleDocument = { hello: 'world' }; - const expectedJson = `{ - "hello": "world" -}`; - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCodeBlock).prop('children')).toEqual(expectedJson); - expect(wrapper.find(EuiCallOut)).toHaveLength(0); - }); - - it('renders invalid documents with error callouts', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find('h3').text()).toEqual('This document was not indexed!'); - expect(wrapper.find(EuiCallOut)).toHaveLength(2); - expect(wrapper.find(EuiCallOut).first().prop('title')).toEqual('Bad JSON error'); - expect(wrapper.find(EuiCallOut).last().prop('title')).toEqual('Schema error'); - }); -}); - -describe('MoreDocumentsText', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('p').text()).toEqual('and 100 other documents.'); - - wrapper.setProps({ documents: 1 }); - expect(wrapper.find('p').text()).toEqual('and 1 other document.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_documents.tsx deleted file mode 100644 index 0dad75cb1f98f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_documents.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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, { Fragment } from 'react'; - -import { EuiCodeBlock, EuiCallOut, EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -interface ExampleDocumentJsonProps { - document: object; - errors?: string[]; -} -export const ExampleDocumentJson: React.FC = ({ document, errors }) => { - return ( - <> - {errors && ( - <> - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.showSummary.documentNotIndexed', - { defaultMessage: 'This document was not indexed!' } - )} -

-
- - {errors.map((errorMessage, index) => ( - - - - - ))} - - )} - - {JSON.stringify(document, null, 2)} - - - - ); -}; - -interface MoreDocumentsTextProps { - documents: number; -} -export const MoreDocumentsText: React.FC = ({ documents }) => { - return ( - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.showSummary.otherDocuments', - { - defaultMessage: - 'and {documents, number} other {documents, plural, one {document} other {documents}}.', - values: { documents }, - } - )} -

-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.scss deleted file mode 100644 index 0343efbaeaf5f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.scss +++ /dev/null @@ -1,20 +0,0 @@ -.documentCreationSummarySection { - padding: $euiSize $euiSizeM; - color: $euiTextSubduedColor; - border-top: $euiBorderThin; - border-bottom: $euiBorderThin; - - & + & { - border-top: 0; - } - - &__title { - display: flex; - align-items: center; - height: $euiSizeL; - - .euiIcon { - margin-right: $euiSizeS; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.test.tsx deleted file mode 100644 index 64d8df98218fc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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, { ReactElement } from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiAccordion, EuiIcon } from '@elastic/eui'; - -import { SummarySectionAccordion, SummarySectionEmpty } from './summary_section'; - -describe('SummarySectionAccordion', () => { - const props = { - id: 'some-id', - status: 'success' as 'success' | 'error' | 'info', - title: 'Some title', - }; - - it('renders', () => { - const wrapper = shallow( - Hello World - ); - - expect(wrapper.type()).toEqual(EuiAccordion); - expect(wrapper.hasClass('documentCreationSummarySection')).toBe(true); - expect(wrapper.find(EuiAccordion).prop('children')).toEqual('Hello World'); - }); - - it('renders a title', () => { - const wrapper = shallow(); - const buttonContent = shallow(wrapper.find(EuiAccordion).prop('buttonContent') as ReactElement); - - expect(buttonContent.find('.documentCreationSummarySection__title').text()).toEqual( - 'Hello World' - ); - }); - - it('renders icons based on the status prop', () => { - const wrapper = shallow(); - const getIcon = () => { - const buttonContent = shallow( - wrapper.find(EuiAccordion).prop('buttonContent') as ReactElement - ); - return buttonContent.find(EuiIcon); - }; - - wrapper.setProps({ status: 'error' }); - expect(getIcon().prop('type')).toEqual('error'); - expect(getIcon().prop('color')).toEqual('danger'); - - wrapper.setProps({ status: 'success' }); - expect(getIcon().prop('type')).toEqual('checkInCircleFilled'); - expect(getIcon().prop('color')).toEqual('success'); - - wrapper.setProps({ status: 'info' }); - expect(getIcon().prop('type')).toEqual('iInCircle'); - expect(getIcon().prop('color')).toEqual('default'); - }); -}); - -describe('SummarySectionEmpty', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.hasClass('documentCreationSummarySection')).toBe(true); - expect(wrapper.find('.documentCreationSummarySection__title').text()).toEqual( - 'No new documents' - ); - expect(wrapper.find(EuiIcon).prop('type')).toEqual('iInCircle'); - expect(wrapper.find(EuiIcon).prop('color')).toEqual('default'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.tsx deleted file mode 100644 index 887231178d39c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_section.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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, { FC, PropsWithChildren } from 'react'; - -import { EuiAccordion, EuiIcon } from '@elastic/eui'; - -import './summary_section.scss'; - -const ICON_PROPS = { - error: { type: 'error', color: 'danger' }, - success: { type: 'checkInCircleFilled', color: 'success' }, - info: { type: 'iInCircle', color: 'default' }, -}; - -interface SummarySectionAccordionProps { - id: string; - status: 'success' | 'error' | 'info'; - title: string; -} -export const SummarySectionAccordion: FC> = ({ - id, - status, - title, - children, -}) => { - return ( - - - {title} - - } - > - {children} - - ); -}; - -interface SummarySectionEmptyProps { - title: string; -} -export const SummarySectionEmpty: React.FC = ({ title }) => { - return ( -
-
- - {title} -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_sections.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_sections.test.tsx deleted file mode 100644 index 7843b8bbf907b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_sections.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBadge } from '@elastic/eui'; - -import { ExampleDocumentJson, MoreDocumentsText } from './summary_documents'; -import { SummarySectionAccordion, SummarySectionEmpty } from './summary_section'; - -import { - InvalidDocumentsSummary, - ValidDocumentsSummary, - SchemaFieldsSummary, -} from './summary_sections'; - -describe('InvalidDocumentsSummary', () => { - const mockDocument = { hello: 'world' }; - const mockExample = { document: mockDocument, errors: ['bad schema'] }; - - it('renders', () => { - setMockValues({ - summary: { - invalidDocuments: { - total: 1, - examples: [mockExample], - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionAccordion).prop('title')).toEqual( - '1 document with errors...' - ); - expect(wrapper.find(ExampleDocumentJson)).toHaveLength(1); - expect(wrapper.find(MoreDocumentsText)).toHaveLength(0); - }); - - it('renders with MoreDocumentsText if more than 5 documents exist', () => { - setMockValues({ - summary: { - invalidDocuments: { - total: 100, - examples: [mockExample, mockExample, mockExample, mockExample, mockExample], - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionAccordion).prop('title')).toEqual( - '100 documents with errors...' - ); - expect(wrapper.find(ExampleDocumentJson)).toHaveLength(5); - expect(wrapper.find(MoreDocumentsText)).toHaveLength(1); - expect(wrapper.find(MoreDocumentsText).prop('documents')).toEqual(95); - }); - - it('does not render if there are no invalid documents', () => { - setMockValues({ - summary: { - invalidDocuments: { - total: 0, - examples: [], - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); - -describe('ValidDocumentsSummary', () => { - const mockDocument = { hello: 'world' }; - - it('renders', () => { - setMockValues({ - summary: { - validDocuments: { - total: 1, - examples: [mockDocument], - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionAccordion).prop('title')).toEqual('Added 1 document.'); - expect(wrapper.find(ExampleDocumentJson)).toHaveLength(1); - expect(wrapper.find(MoreDocumentsText)).toHaveLength(0); - }); - - it('renders with MoreDocumentsText if more than 5 documents exist', () => { - setMockValues({ - summary: { - validDocuments: { - total: 7, - examples: [mockDocument, mockDocument, mockDocument, mockDocument, mockDocument], - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionAccordion).prop('title')).toEqual('Added 7 documents.'); - expect(wrapper.find(ExampleDocumentJson)).toHaveLength(5); - expect(wrapper.find(MoreDocumentsText)).toHaveLength(1); - expect(wrapper.find(MoreDocumentsText).prop('documents')).toEqual(2); - }); - - it('renders SummarySectionEmpty if there are no valid documents', () => { - setMockValues({ - summary: { - validDocuments: { - total: 0, - examples: [], - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionEmpty).prop('title')).toEqual('No new documents.'); - }); -}); - -describe('SchemaFieldsSummary', () => { - it('renders', () => { - setMockValues({ - summary: { - newSchemaFields: ['test'], - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionAccordion).prop('title')).toEqual( - "Added 1 field to the Engine's schema." - ); - expect(wrapper.find(EuiBadge)).toHaveLength(1); - }); - - it('renders multiple new schema fields', () => { - setMockValues({ - summary: { - newSchemaFields: ['foo', 'bar', 'baz', 'qux', 'quux', 'quuz'], - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionAccordion).prop('title')).toEqual( - "Added 6 fields to the Engine's schema." - ); - expect(wrapper.find(EuiBadge)).toHaveLength(6); - }); - - it('renders SummarySectionEmpty if there are no new schema fields', () => { - setMockValues({ - summary: { - newSchemaFields: [], - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SummarySectionEmpty).prop('title')).toEqual('No new schema fields.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_sections.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_sections.tsx deleted file mode 100644 index e156b0fb0a708..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_response_components/summary_sections.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic } from '..'; - -import { ExampleDocumentJson, MoreDocumentsText } from './summary_documents'; -import { SummarySectionAccordion, SummarySectionEmpty } from './summary_section'; - -export const InvalidDocumentsSummary: React.FC = () => { - const { - summary: { invalidDocuments }, - } = useValues(DocumentCreationLogic); - - const hasInvalidDocuments = invalidDocuments.total > 0; - const unshownInvalidDocuments = invalidDocuments.total - invalidDocuments.examples.length; - - return hasInvalidDocuments ? ( - - {invalidDocuments.examples.map(({ document, errors }, index) => ( - - ))} - {unshownInvalidDocuments > 0 && } - - ) : null; -}; - -export const ValidDocumentsSummary: React.FC = () => { - const { - summary: { validDocuments }, - } = useValues(DocumentCreationLogic); - - const hasValidDocuments = validDocuments.total > 0; - const unshownValidDocuments = validDocuments.total - validDocuments.examples.length; - - return hasValidDocuments ? ( - - {validDocuments.examples.map((document, index) => ( - - ))} - {unshownValidDocuments > 0 && } - - ) : ( - - ); -}; - -export const SchemaFieldsSummary: React.FC = () => { - const { - summary: { newSchemaFields }, - } = useValues(DocumentCreationLogic); - - return newSchemaFields.length ? ( - - - {newSchemaFields.map((schemaField: string) => ( - - {schemaField} - - ))} - - - ) : ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx deleted file mode 100644 index a99987272fe23..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions } from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; -import { useLocation } from 'react-router-dom'; - -import { shallow } from 'enzyme'; - -import { EuiCard, EuiText } from '@elastic/eui'; - -import { EuiCardTo } from '../../../shared/react_router_helpers'; - -import { DocumentCreationButtons } from '.'; - -describe('DocumentCreationButtons', () => { - const actions = { - openDocumentCreation: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCard)).toHaveLength(2); - expect(wrapper.find(EuiCardTo)).toHaveLength(1); - }); - - it('renders with disabled buttons', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCard).first().prop('isDisabled')).toEqual(true); - expect(wrapper.find(EuiCardTo).prop('isDisabled')).toEqual(true); - }); - - it('renders with flyoutHeader', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiText)).toHaveLength(1); - }); - - it('opens the DocumentCreationFlyout on click', () => { - const wrapper = shallow(); - - wrapper.find(EuiCard).at(0).simulate('click'); - expect(actions.openDocumentCreation).toHaveBeenCalledWith('json'); - - wrapper.find(EuiCard).at(1).simulate('click'); - expect(actions.openDocumentCreation).toHaveBeenCalledWith('api'); - }); - - it('renders the crawler button with a link to the crawler page', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/some-engine/crawler'); - }); - - it('calls openDocumentCreation("json") if ?method=json', () => { - const search = '?method=json'; - (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); - - shallow(); - expect(actions.openDocumentCreation).toHaveBeenCalledWith('json'); - }); - - it('calls openDocumentCreation("api") if ?method=api', () => { - const search = '?method=api'; - (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); - - shallow(); - expect(actions.openDocumentCreation).toHaveBeenCalledWith('api'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx deleted file mode 100644 index 80e087e007671..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useLocation } from 'react-router-dom'; - -import { Location } from 'history'; -import { useActions } from 'kea'; - -import { - EuiEmptyPrompt, - EuiText, - EuiTitle, - EuiImage, - EuiLink, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - EuiCard, - EuiIcon, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../shared/doc_links'; -import { parseQueryParams } from '../../../shared/query_params'; -import { EuiCardTo } from '../../../shared/react_router_helpers'; -import { ENGINE_CRAWLER_PATH } from '../../routes'; -import { generateEnginePath } from '../engine'; - -import illustration from './illustration.svg'; - -import { DocumentCreationLogic } from '.'; - -interface Props { - isFlyout?: boolean; - disabled?: boolean; -} - -export const DocumentCreationButtons: React.FC = ({ - isFlyout = false, - disabled = false, -}) => { - const { openDocumentCreation } = useActions(DocumentCreationLogic); - - const { search } = useLocation() as Location; - const { method } = parseQueryParams(search); - - useEffect(() => { - switch (method) { - case 'json': - openDocumentCreation('json'); - break; - case 'api': - openDocumentCreation('api'); - break; - } - }, []); - - const crawlerLink = generateEnginePath(ENGINE_CRAWLER_PATH); - - const helperText = ( -

- {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.helperText', { - defaultMessage: - 'There are three ways to send documents to your engine for indexing. You can paste or upload a JSON file, POST to the documents API endpoint, or use the Elastic Web Crawler to automatically index documents from a URL.', - })} -

- ); - - const emptyState = ( - - - } - title={ -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateTitle', - { defaultMessage: 'Add documents' } - )} -

- } - layout="horizontal" - hasBorder - color="plain" - body={helperText} - footer={ - <> - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterText', - { defaultMessage: 'Want to learn more about indexing documents?' } - )} - - {' '} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterLink', - { defaultMessage: 'Read documentation' } - )} - - - } - /> -
- ); - - const flyoutHeader = ( - <> - {helperText} - - - ); - - return ( - <> - {isFlyout && flyoutHeader} - - - } - to={crawlerLink} - isDisabled={disabled} - /> - - } - data-test-subj="IndexingPasteJSONButton" - onClick={() => openDocumentCreation('json')} - isDisabled={disabled} - /> - - } - onClick={() => openDocumentCreation('api')} - isDisabled={disabled} - /> - - {!isFlyout && emptyState} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx deleted file mode 100644 index a5d0e8facf236..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFlyout } from '@elastic/eui'; - -import { - ShowCreationModes, - ApiCodeExample, - JsonFlyout, - ElasticsearchIndex, -} from './creation_mode_components'; -import { Summary } from './creation_response_components'; -import { DocumentCreationFlyout, FlyoutContent } from './document_creation_flyout'; -import { DocumentCreationStep } from './types'; - -describe('DocumentCreationFlyout', () => { - const values = { - isDocumentCreationOpen: true, - creationMode: 'api', - creationStep: DocumentCreationStep.AddDocuments, - }; - const actions = { - closeDocumentCreation: jest.fn(), - setActiveJsonTab: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a closeable flyout', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFlyout)).toHaveLength(1); - - wrapper.find(EuiFlyout).prop('onClose')(new MouseEvent('click')); - expect(actions.closeDocumentCreation).toHaveBeenCalled(); - }); - - it('does not render if isDocumentCreationOpen is false', () => { - setMockValues({ ...values, isDocumentCreationOpen: false }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - describe('FlyoutContent', () => { - it('renders ShowCreationModes', () => { - setMockValues({ ...values, creationStep: DocumentCreationStep.ShowCreationModes }); - const wrapper = shallow(); - - expect(wrapper.find(ShowCreationModes)).toHaveLength(1); - }); - - describe('creation modes', () => { - it('renders ApiCodeExample', () => { - setMockValues({ ...values, creationMode: 'api' }); - const wrapper = shallow(); - - expect(wrapper.find(ApiCodeExample)).toHaveLength(1); - }); - - it('renders JsonFlyout', () => { - setMockValues({ ...values, creationMode: 'json' }); - const wrapper = shallow(); - - expect(wrapper.find(JsonFlyout)).toHaveLength(1); - }); - - it('renders ElasticsearchIndex', () => { - setMockValues({ ...values, creationMode: 'elasticsearchIndex' }); - const wrapper = shallow(); - - expect(wrapper.find(ElasticsearchIndex)).toHaveLength(1); - }); - }); - - it('renders a summary', () => { - setMockValues({ ...values, creationStep: DocumentCreationStep.ShowSummary }); - const wrapper = shallow(); - - expect(wrapper.find(Summary)).toHaveLength(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx deleted file mode 100644 index ad137906aeb92..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiPortal, EuiFlyout } from '@elastic/eui'; - -import { FLYOUT_ARIA_LABEL_ID } from './constants'; -import { - ShowCreationModes, - ApiCodeExample, - JsonFlyout, - ElasticsearchIndex, -} from './creation_mode_components'; -import { Summary } from './creation_response_components'; -import { DocumentCreationStep } from './types'; - -import { DocumentCreationLogic } from '.'; - -export const DocumentCreationFlyout: React.FC = () => { - const { closeDocumentCreation } = useActions(DocumentCreationLogic); - const { isDocumentCreationOpen } = useValues(DocumentCreationLogic); - - return isDocumentCreationOpen ? ( - - - - - - ) : null; -}; - -export const FlyoutContent: React.FC = () => { - const { creationStep, creationMode } = useValues(DocumentCreationLogic); - - switch (creationStep) { - case DocumentCreationStep.ShowCreationModes: - return ; - case DocumentCreationStep.AddDocuments: - switch (creationMode) { - case 'api': - return ; - case 'json': - return ; - case 'elasticsearchIndex': - return ; - } - case DocumentCreationStep.ShowSummary: - return ; - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts deleted file mode 100644 index 925f5af35ff48..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts +++ /dev/null @@ -1,606 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../__mocks__/kea_logic'; - -import dedent from 'dedent'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -jest.mock('../engine', () => ({ - EngineLogic: { values: { engineName: 'test-engine' } }, -})); - -import { DOCUMENTS_API_JSON_EXAMPLE } from './constants'; -import { DocumentCreationStep } from './types'; - -jest.mock('./utils', () => ({ - readUploadedFileAsText: jest.fn(), -})); -import { readUploadedFileAsText } from './utils'; - -import { DocumentCreationLogic } from '.'; - -describe('DocumentCreationLogic', () => { - const { mount } = new LogicMounter(DocumentCreationLogic); - const { http } = mockHttpValues; - - const DEFAULT_VALUES = { - isDocumentCreationOpen: false, - creationMode: 'api', - activeJsonTab: 'uploadTab', - creationStep: DocumentCreationStep.AddDocuments, - textInput: dedent(DOCUMENTS_API_JSON_EXAMPLE), - fileInput: null, - isUploading: false, - warnings: [], - errors: [], - summary: {}, - }; - const mockFile = new File(['mockFile'], 'mockFile.json'); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(DocumentCreationLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('showCreationModes', () => { - beforeAll(() => { - mount(); - DocumentCreationLogic.actions.showCreationModes(); - }); - - const EXPECTED_VALUES = { - ...DEFAULT_VALUES, - isDocumentCreationOpen: true, - creationStep: DocumentCreationStep.ShowCreationModes, - }; - - describe('isDocumentCreationOpen', () => { - it('should be set to true', () => { - expect(DocumentCreationLogic.values).toEqual({ - ...EXPECTED_VALUES, - isDocumentCreationOpen: true, - }); - }); - }); - - describe('creationStep', () => { - it('should be set to ShowCreationModes', () => { - expect(DocumentCreationLogic.values).toEqual({ - ...EXPECTED_VALUES, - creationStep: DocumentCreationStep.ShowCreationModes, - }); - }); - }); - }); - - describe('openDocumentCreation', () => { - beforeAll(() => { - mount(); - DocumentCreationLogic.actions.openDocumentCreation('api'); - }); - - const EXPECTED_VALUES = { - ...DEFAULT_VALUES, - isDocumentCreationOpen: true, - creationStep: DocumentCreationStep.AddDocuments, - creationMode: 'api', - }; - - describe('isDocumentCreationOpen', () => { - it('should be set to true', () => { - expect(DocumentCreationLogic.values).toEqual({ - ...EXPECTED_VALUES, - isDocumentCreationOpen: true, - }); - }); - }); - - describe('creationStep', () => { - it('should be set to AddDocuments', () => { - expect(DocumentCreationLogic.values).toEqual({ - ...EXPECTED_VALUES, - creationStep: DocumentCreationStep.AddDocuments, - }); - }); - }); - - describe('creationMode', () => { - it('should be set to the provided value', () => { - expect(DocumentCreationLogic.values).toEqual({ - ...EXPECTED_VALUES, - creationMode: 'api', - }); - }); - }); - }); - - describe('setActiveJsonTab', () => { - beforeAll(() => { - mount(); - DocumentCreationLogic.actions.setActiveJsonTab('pasteTab'); - }); - - const EXPECTED_VALUES = { - ...DEFAULT_VALUES, - activeJsonTab: 'pasteTab', - }; - - describe('isDocumentCreationOpen', () => { - it('should be set to "pasteTab"', () => { - expect(DocumentCreationLogic.values).toEqual(EXPECTED_VALUES); - }); - }); - }); - - describe('closeDocumentCreation', () => { - describe('isDocumentCreationOpen', () => { - it('should be set to false', () => { - mount(); - DocumentCreationLogic.actions.closeDocumentCreation(); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isDocumentCreationOpen: false, - }); - }); - }); - - describe('errors & warnings', () => { - it('should be cleared', () => { - mount({ errors: ['error'], warnings: ['warnings'] }); - DocumentCreationLogic.actions.closeDocumentCreation(); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - errors: [], - warnings: [], - }); - }); - }); - - describe('textInput & fileInput', () => { - it('should be reset to default values', () => { - mount({ textInput: 'test', fileInput: mockFile }); - DocumentCreationLogic.actions.closeDocumentCreation(); - - expect(DocumentCreationLogic.values).toEqual(DEFAULT_VALUES); - }); - }); - }); - - describe('setCreationStep', () => { - describe('creationStep', () => { - it('should be set to the provided value', () => { - mount(); - DocumentCreationLogic.actions.setCreationStep(DocumentCreationStep.ShowSummary); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - creationStep: 2, - }); - }); - }); - }); - - describe('setTextInput', () => { - describe('textInput', () => { - it('should be set to the provided value', () => { - mount(); - DocumentCreationLogic.actions.setTextInput('hello world'); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - textInput: 'hello world', - }); - }); - }); - }); - - describe('setFileInput', () => { - describe('fileInput', () => { - it('should be set to the provided value', () => { - mount(); - DocumentCreationLogic.actions.setFileInput(mockFile); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - fileInput: mockFile, - }); - }); - }); - }); - - describe('setWarnings', () => { - describe('warnings', () => { - it('should be set to the provided value', () => { - mount(); - DocumentCreationLogic.actions.setWarnings(['warning!']); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - warnings: ['warning!'], - }); - }); - }); - }); - - describe('setErrors', () => { - describe('errors', () => { - beforeAll(() => { - mount(); - }); - - it('should be set to the provided value', () => { - DocumentCreationLogic.actions.setErrors(['error 1', 'error 2']); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - errors: ['error 1', 'error 2'], - }); - }); - - it('should gracefully array wrap single errors', () => { - DocumentCreationLogic.actions.setErrors('error'); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - errors: ['error'], - }); - }); - }); - - describe('isUploading', () => { - it('resets isUploading to false', () => { - mount({ isUploading: true }); - DocumentCreationLogic.actions.setErrors(['error']); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - errors: ['error'], - isUploading: false, - }); - }); - }); - }); - - describe('setSummary', () => { - const mockSummary = { - errors: [], - validDocuments: { - total: 1, - examples: [{ foo: 'bar' }], - }, - invalidDocuments: { - total: 0, - examples: [], - }, - newSchemaFields: ['foo'], - }; - - describe('summary', () => { - it('should be set to the provided value', () => { - mount(); - DocumentCreationLogic.actions.setSummary(mockSummary); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - summary: mockSummary, - }); - }); - }); - - describe('isUploading', () => { - it('resets isUploading to false', () => { - mount({ isUploading: true }); - DocumentCreationLogic.actions.setSummary(mockSummary); - - expect(DocumentCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - summary: mockSummary, - isUploading: false, - }); - }); - }); - }); - }); - - describe('listeners', () => { - describe('onSubmitFile', () => { - describe('with a valid file', () => { - beforeAll(() => { - mount({ fileInput: mockFile }); - jest.spyOn(DocumentCreationLogic.actions, 'onSubmitJson').mockImplementation(); - }); - - it('should read the text in the file and submit it as JSON', async () => { - (readUploadedFileAsText as jest.Mock).mockReturnValue(Promise.resolve('some mock text')); - await DocumentCreationLogic.actions.onSubmitFile(); - - expect(DocumentCreationLogic.values.textInput).toEqual('some mock text'); - expect(DocumentCreationLogic.actions.onSubmitJson).toHaveBeenCalled(); - }); - - it('should set isUploading to true', () => { - DocumentCreationLogic.actions.onSubmitFile(); - - expect(DocumentCreationLogic.values.isUploading).toEqual(true); - }); - }); - - describe('with an invalid file', () => { - beforeAll(() => { - mount({ fileInput: mockFile }); - jest.spyOn(DocumentCreationLogic.actions, 'onSubmitJson'); - jest.spyOn(DocumentCreationLogic.actions, 'setErrors'); - }); - - it('should return an error', async () => { - (readUploadedFileAsText as jest.Mock).mockReturnValue(Promise.reject()); - await DocumentCreationLogic.actions.onSubmitFile(); - - expect(DocumentCreationLogic.actions.onSubmitJson).not.toHaveBeenCalled(); - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith([ - 'Problem parsing file.', - ]); - }); - }); - - describe('without a file', () => { - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'onSubmitJson'); - jest.spyOn(DocumentCreationLogic.actions, 'setErrors'); - }); - - it('should return an error', () => { - DocumentCreationLogic.actions.onSubmitFile(); - - expect(DocumentCreationLogic.actions.onSubmitJson).not.toHaveBeenCalled(); - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith(['No file found.']); - }); - }); - }); - - describe('onSubmitJson', () => { - describe('with large JSON files', () => { - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'uploadDocuments').mockImplementation(); - jest.spyOn(DocumentCreationLogic.actions, 'setWarnings'); - }); - - it('should set a warning', () => { - jest.spyOn(global.Buffer, 'byteLength').mockImplementation(() => 55000000); // 55MB - DocumentCreationLogic.actions.onSubmitJson(); - - expect(DocumentCreationLogic.actions.setWarnings).toHaveBeenCalledWith([ - expect.stringContaining("You're uploading an extremely large file"), - ]); - - jest.restoreAllMocks(); - }); - }); - - describe('with invalid JSON', () => { - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'uploadDocuments').mockImplementation(); - jest.spyOn(DocumentCreationLogic.actions, 'setErrors'); - }); - - it('should return malformed JSON errors', () => { - DocumentCreationLogic.actions.setTextInput('invalid JSON'); - DocumentCreationLogic.actions.onSubmitJson(); - - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith([ - `Unexpected token 'i', "invalid JSON" is not valid JSON`, - ]); - expect(DocumentCreationLogic.actions.uploadDocuments).not.toHaveBeenCalled(); - }); - - it('should error on non-array/object JSON', () => { - DocumentCreationLogic.actions.setTextInput('null'); - DocumentCreationLogic.actions.onSubmitJson(); - - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith([ - 'Document contents must be a valid JSON array or object.', - ]); - expect(DocumentCreationLogic.actions.uploadDocuments).not.toHaveBeenCalled(); - }); - }); - - describe('with valid JSON', () => { - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'uploadDocuments').mockImplementation(); - jest.spyOn(DocumentCreationLogic.actions, 'setErrors'); - }); - - it('should accept an array of JSON objs', () => { - const mockJson = [{ foo: 'bar' }, { bar: 'baz' }]; - DocumentCreationLogic.actions.setTextInput('[{"foo":"bar"},{"bar":"baz"}]'); - DocumentCreationLogic.actions.onSubmitJson(); - - expect(DocumentCreationLogic.actions.uploadDocuments).toHaveBeenCalledWith({ - documents: mockJson, - }); - expect(DocumentCreationLogic.actions.setErrors).not.toHaveBeenCalled(); - }); - - it('should accept a single JSON obj', () => { - const mockJson = { foo: 'bar' }; - DocumentCreationLogic.actions.setTextInput('{"foo":"bar"}'); - DocumentCreationLogic.actions.onSubmitJson(); - - expect(DocumentCreationLogic.actions.uploadDocuments).toHaveBeenCalledWith({ - documents: [mockJson], - }); - expect(DocumentCreationLogic.actions.setErrors).not.toHaveBeenCalled(); - }); - }); - }); - - describe('uploadDocuments', () => { - describe('valid uploads', () => { - const mockValidDocuments = [{ foo: 'bar', bar: 'baz', qux: 'quux' }]; - const mockValidResponse = { - errors: [], - validDocuments: { total: 3, examples: mockValidDocuments }, - invalidDocuments: { total: 0, examples: [] }, - newSchemaFields: ['foo', 'bar', 'qux'], - }; - - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'setSummary'); - jest.spyOn(DocumentCreationLogic.actions, 'setCreationStep'); - }); - - it('should set and show summary from the returned response', async () => { - http.post.mockReturnValueOnce(Promise.resolve(mockValidResponse)); - - await DocumentCreationLogic.actions.uploadDocuments({ documents: mockValidDocuments }); - await nextTick(); - - expect(DocumentCreationLogic.actions.setSummary).toHaveBeenCalledWith(mockValidResponse); - expect(DocumentCreationLogic.actions.setCreationStep).toHaveBeenCalledWith( - DocumentCreationStep.ShowSummary - ); - }); - }); - - describe('invalid uploads', () => { - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'setErrors'); - }); - - it('handles API errors', async () => { - http.post.mockReturnValueOnce( - Promise.reject({ - body: { - statusCode: 400, - error: 'Bad Request', - message: 'Invalid request payload JSON format', - }, - }) - ); - - await DocumentCreationLogic.actions.uploadDocuments({ documents: [{}] }); - await nextTick(); - - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith( - '[400 Bad Request] Invalid request payload JSON format' - ); - }); - - it('handles client-side errors', async () => { - (http.post as jest.Mock).mockReturnValueOnce(new Error()); - - await DocumentCreationLogic.actions.uploadDocuments({ documents: [{}] }); - await nextTick(); - - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith( - "Cannot read properties of undefined (reading 'total')" - ); - }); - - // NOTE: I can't seem to reproduce this in a production setting. - it('handles errors returned from the API', async () => { - http.post.mockReturnValueOnce( - Promise.resolve({ - errors: ['JSON cannot be empty'], - }) - ); - - await DocumentCreationLogic.actions.uploadDocuments({ documents: [{}] }); - await nextTick(); - - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith([ - 'JSON cannot be empty', - ]); - }); - }); - - describe('chunks large uploads', () => { - // Using an array of #s for speed, it doesn't really matter what the contents of the documents are for this test - const largeDocumentsArray = [...Array(200).keys()] as unknown as object[]; - - const mockFirstResponse = { - validDocuments: { total: 99, examples: largeDocumentsArray.slice(0, 98) }, - invalidDocuments: { - total: 1, - examples: [{ document: largeDocumentsArray[99], error: ['some error'] }], - }, - newSchemaFields: ['foo', 'bar'], - }; - const mockSecondResponse = { - validDocuments: { total: 99, examples: largeDocumentsArray.slice(1, 99) }, - invalidDocuments: { - total: 1, - examples: [{ document: largeDocumentsArray[0], error: ['another error'] }], - }, - newSchemaFields: ['bar', 'baz'], - }; - - beforeAll(() => { - mount(); - jest.spyOn(DocumentCreationLogic.actions, 'setSummary'); - jest.spyOn(DocumentCreationLogic.actions, 'setErrors'); - }); - - it('should correctly merge multiple API calls into a single summary obj', async () => { - (http.post as jest.Mock) - .mockReturnValueOnce(mockFirstResponse) - .mockReturnValueOnce(mockSecondResponse); - - await DocumentCreationLogic.actions.uploadDocuments({ documents: largeDocumentsArray }); - await nextTick(); - - expect(http.post).toHaveBeenCalledTimes(2); - expect(DocumentCreationLogic.actions.setSummary).toHaveBeenCalledWith({ - errors: [], - validDocuments: { - total: 198, - examples: largeDocumentsArray.slice(0, 5), - }, - invalidDocuments: { - total: 2, - examples: [ - { document: largeDocumentsArray[99], error: ['some error'] }, - { document: largeDocumentsArray[0], error: ['another error'] }, - ], - }, - newSchemaFields: ['foo', 'bar', 'baz'], - }); - }); - - it('should correctly merge response errors', async () => { - (http.post as jest.Mock) - .mockReturnValueOnce({ ...mockFirstResponse, errors: ['JSON cannot be empty'] }) - .mockReturnValueOnce({ ...mockSecondResponse, errors: ['Too large to render'] }); - - await DocumentCreationLogic.actions.uploadDocuments({ documents: largeDocumentsArray }); - await nextTick(); - - expect(http.post).toHaveBeenCalledTimes(2); - expect(DocumentCreationLogic.actions.setErrors).toHaveBeenCalledWith([ - 'JSON cannot be empty', - 'Too large to render', - ]); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts deleted file mode 100644 index b89d0fddf2627..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 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 dedent from 'dedent'; -import { kea, MakeLogicType } from 'kea'; -import { isPlainObject, chunk, uniq } from 'lodash'; - -import { HttpLogic } from '../../../shared/http'; -import { EngineLogic } from '../engine'; - -import { - DOCUMENTS_API_JSON_EXAMPLE, - DOCUMENT_CREATION_ERRORS, - DOCUMENT_CREATION_WARNINGS, -} from './constants'; -import { DocumentCreationMode, DocumentCreationStep, DocumentCreationSummary } from './types'; -import { readUploadedFileAsText } from './utils'; - -export type ActiveJsonTab = 'uploadTab' | 'pasteTab'; - -interface DocumentCreationValues { - isDocumentCreationOpen: boolean; - creationMode: DocumentCreationMode; - creationStep: DocumentCreationStep; - activeJsonTab: ActiveJsonTab; - textInput: string; - fileInput: File | null; - isUploading: boolean; - warnings: string[]; - errors: string[]; - summary: DocumentCreationSummary; -} - -interface DocumentCreationActions { - showCreationModes(): void; - openDocumentCreation(creationMode: DocumentCreationMode): { creationMode: DocumentCreationMode }; - closeDocumentCreation(): void; - setCreationStep(creationStep: DocumentCreationStep): { creationStep: DocumentCreationStep }; - setActiveJsonTab(activeJsonTab: ActiveJsonTab): { activeJsonTab: ActiveJsonTab }; - setTextInput(textInput: string): { textInput: string }; - setFileInput(fileInput: File | null): { fileInput: File | null }; - setWarnings(warnings: string[]): { warnings: string[] }; - setErrors(errors: string[] | string): { errors: string[] }; - setSummary(summary: DocumentCreationSummary): { summary: DocumentCreationSummary }; - onSubmitFile(): void; - onSubmitJson(): void; - uploadDocuments(args: { documents: object[] }): { documents: object[] }; -} - -export const DocumentCreationLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'document_creation_logic'], - actions: () => ({ - showCreationModes: () => null, - openDocumentCreation: (creationMode) => ({ creationMode }), - closeDocumentCreation: () => null, - setCreationStep: (creationStep) => ({ creationStep }), - setActiveJsonTab: (activeJsonTab) => ({ activeJsonTab }), - setTextInput: (textInput) => ({ textInput }), - setFileInput: (fileInput) => ({ fileInput }), - setWarnings: (warnings) => ({ warnings }), - setErrors: (errors) => ({ errors }), - setSummary: (summary) => ({ summary }), - onSubmitJson: () => null, - onSubmitFile: () => null, - uploadDocuments: ({ documents }) => ({ documents }), - }), - reducers: () => ({ - isDocumentCreationOpen: [ - false, - { - showCreationModes: () => true, - openDocumentCreation: () => true, - closeDocumentCreation: () => false, - }, - ], - creationMode: [ - 'api', - { - // @ts-expect-error upgrade typescript v5.1.6 - openDocumentCreation: (_, { creationMode }) => creationMode, - }, - ], - activeJsonTab: [ - 'uploadTab', - { - // @ts-expect-error upgrade typescript v5.1.6 - setActiveJsonTab: (_, { activeJsonTab }) => activeJsonTab, - }, - ], - creationStep: [ - DocumentCreationStep.AddDocuments, - { - showCreationModes: () => DocumentCreationStep.ShowCreationModes, - openDocumentCreation: () => DocumentCreationStep.AddDocuments, - // @ts-expect-error upgrade typescript v5.1.6 - setCreationStep: (_, { creationStep }) => creationStep, - }, - ], - textInput: [ - dedent(DOCUMENTS_API_JSON_EXAMPLE), - { - // @ts-expect-error upgrade typescript v5.1.6 - setTextInput: (_, { textInput }) => textInput, - closeDocumentCreation: () => dedent(DOCUMENTS_API_JSON_EXAMPLE), - setActiveJsonTab: () => dedent(DOCUMENTS_API_JSON_EXAMPLE), - }, - ], - fileInput: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - setFileInput: (_, { fileInput }) => fileInput, - closeDocumentCreation: () => null, - setActiveJsonTab: () => null, - }, - ], - isUploading: [ - false, - { - onSubmitFile: () => true, - onSubmitJson: () => true, - setErrors: () => false, - setSummary: () => false, - setActiveJsonTab: () => false, - }, - ], - warnings: [ - [], - { - onSubmitJson: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - setWarnings: (_, { warnings }) => warnings, - closeDocumentCreation: () => [], - setActiveJsonTab: () => [], - }, - ], - errors: [ - [], - { - onSubmitJson: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - setErrors: (_, { errors }) => (Array.isArray(errors) ? errors : [errors]), - closeDocumentCreation: () => [], - setActiveJsonTab: () => [], - }, - ], - summary: [ - {} as DocumentCreationSummary, - { - // @ts-expect-error upgrade typescript v5.1.6 - setSummary: (_, { summary }) => summary, - }, - ], - }), - listeners: ({ values, actions }) => ({ - onSubmitFile: async () => { - const { fileInput } = values; - - if (!fileInput) { - return actions.setErrors([DOCUMENT_CREATION_ERRORS.NO_FILE]); - } - try { - const textInput = await readUploadedFileAsText(fileInput); - actions.setTextInput(textInput); - actions.onSubmitJson(); - } catch { - actions.setErrors([DOCUMENT_CREATION_ERRORS.NO_VALID_FILE]); - } - }, - onSubmitJson: () => { - const { textInput } = values; - - const MAX_UPLOAD_BYTES = 50 * 1000000; // 50 MB - if (Buffer.byteLength(textInput) > MAX_UPLOAD_BYTES) { - actions.setWarnings([DOCUMENT_CREATION_WARNINGS.LARGE_FILE]); - } - - let documents; - try { - documents = JSON.parse(textInput); - } catch (error) { - return actions.setErrors([error.message]); - } - - if (Array.isArray(documents)) { - actions.uploadDocuments({ documents }); - } else if (isPlainObject(documents)) { - actions.uploadDocuments({ documents: [documents] }); - } else { - actions.setErrors([DOCUMENT_CREATION_ERRORS.NOT_VALID]); - } - }, - uploadDocuments: async ({ documents }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const CHUNK_SIZE = 100; - const MAX_EXAMPLES = 5; - - const promises = chunk(documents, CHUNK_SIZE).map((documentsChunk) => { - const body = JSON.stringify({ documents: documentsChunk }); - return http.post( - `/internal/app_search/engines/${engineName}/documents`, - { body } - ); - }); - - try { - const responses = await Promise.all(promises); - const summary: DocumentCreationSummary = { - errors: [], - validDocuments: { total: 0, examples: [] }, - invalidDocuments: { total: 0, examples: [] }, - newSchemaFields: [], - }; - responses.forEach((response) => { - if (response.errors?.length > 0) { - summary.errors = uniq([...summary.errors, ...response.errors]); - return; - } - summary.validDocuments.total += response.validDocuments.total; - summary.invalidDocuments.total += response.invalidDocuments.total; - summary.validDocuments.examples = [ - ...summary.validDocuments.examples, - ...response.validDocuments.examples, - ].slice(0, MAX_EXAMPLES); - summary.invalidDocuments.examples = [ - ...summary.invalidDocuments.examples, - ...response.invalidDocuments.examples, - ].slice(0, MAX_EXAMPLES); - summary.newSchemaFields = uniq([...summary.newSchemaFields, ...response.newSchemaFields]); - }); - - if (summary.errors.length > 0) { - actions.setErrors(summary.errors); - } else { - actions.setSummary(summary); - actions.setCreationStep(DocumentCreationStep.ShowSummary); - } - } catch ({ body, message }) { - const errors = body ? `[${body.statusCode} ${body.error}] ${body.message}` : message; - actions.setErrors(errors); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/illustration.svg b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/illustration.svg deleted file mode 100644 index 6af40daf69d60..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/illustration.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts deleted file mode 100644 index 4bf51e50aad30..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 { DocumentCreationButtons } from './document_creation_buttons'; -export { DocumentCreationFlyout } from './document_creation_flyout'; -export { DocumentCreationLogic } from './document_creation_logic'; -export type { ActiveJsonTab } from './document_creation_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/types.ts deleted file mode 100644 index 33360f4673088..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 type DocumentCreationMode = 'api' | 'json' | 'elasticsearchIndex'; - -export enum DocumentCreationStep { - ShowCreationModes, - AddDocuments, - ShowSummary, -} - -export interface DocumentCreationSummary { - errors: string[]; - validDocuments: { - total: number; - examples: object[]; - }; - invalidDocuments: { - total: number; - examples: Array<{ - document: object; - errors: string[]; - }>; - }; - newSchemaFields: string[]; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/utils.test.ts deleted file mode 100644 index 3a5bb36477f53..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/utils.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { readUploadedFileAsText } from './utils'; - -describe('readUploadedFileAsText', () => { - it('reads a file as text', async () => { - const file = new File(['a mock file'], 'mockFile.json'); - const text = await readUploadedFileAsText(file); - expect(text).toEqual('a mock file'); - }); - - it('throws an error if the file cannot be read', async () => { - const badFile = 'causes an error' as unknown as File; - await expect(readUploadedFileAsText(badFile)).rejects.toThrow(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/utils.ts deleted file mode 100644 index c4e8e54057545..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 const readUploadedFileAsText = (fileInput: File): Promise => { - const reader = new FileReader(); - - return new Promise((resolve, reject) => { - reader.onload = () => { - resolve(reader.result as string); - }; - try { - reader.readAsText(fileInput); - } catch { - reader.abort(); - reject(new Error()); - } - }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.test.tsx deleted file mode 100644 index e9b74a56d0e50..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { DocumentCreationFlyout } from '../../document_creation'; - -import { DocumentCreationButton } from './document_creation_button'; - -describe('DocumentCreationButton', () => { - let wrapper: ShallowWrapper; - const showCreationModes = jest.fn(); - - beforeAll(() => { - setMockActions({ showCreationModes }); - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.find(EuiButton).length).toEqual(1); - expect(wrapper.find(DocumentCreationFlyout).length).toEqual(1); - }); - - it('opens the document creation modes modal on click', () => { - wrapper.find(EuiButton).simulate('click'); - expect(showCreationModes).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx deleted file mode 100644 index 482ee282cf464..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/document_creation_button.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DocumentCreationLogic, DocumentCreationFlyout } from '../../document_creation'; - -export const DocumentCreationButton: React.FC = () => { - const { showCreationModes } = useActions(DocumentCreationLogic); - - return ( - <> - - {i18n.translate('xpack.enterpriseSearch.appSearch.documents.indexDocuments', { - defaultMessage: 'Index documents', - })} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx deleted file mode 100644 index 88f255f5a3a47..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Add your first documents'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchIndexingDocs) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx deleted file mode 100644 index a311899d380e9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyState = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.documents.empty.title', { - defaultMessage: 'Add your first documents', - })} - - } - body={ -

- {i18n.translate('xpack.enterpriseSearch.appSearch.documents.empty.description', { - defaultMessage: - 'You can index documents using the App Search Web Crawler, by uploading JSON, or by using the API.', - })} -

- } - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.documents.empty.buttonLabel', { - defaultMessage: 'Read the documents guide', - })} - - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/index.ts deleted file mode 100644 index af6425d7c610a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { DocumentCreationButton } from './document_creation_button'; -export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/constants.ts deleted file mode 100644 index 315286277da21..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const DOCUMENTS_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.documents.title', { - defaultMessage: 'Documents', -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.test.tsx deleted file mode 100644 index 7184db03b615b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../__mocks__/react_router'; -import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiPanel, EuiBasicTable } from '@elastic/eui'; - -import { getPageHeaderActions } from '../../../test_helpers'; - -import { ResultFieldValue } from '../result'; - -import { DocumentDetail } from '.'; - -describe('DocumentDetail', () => { - const values = { - isMetaEngine: false, - isElasticsearchEngine: false, - dataLoading: false, - fields: [], - }; - - const actions = { - deleteDocument: jest.fn(), - getDocumentDetails: jest.fn(), - setFields: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - - mockUseParams.mockImplementationOnce(() => ({ - documentId: '1', - })); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPanel).length).toBe(1); - }); - - it('initializes data on mount', () => { - shallow(); - expect(actions.getDocumentDetails).toHaveBeenCalledWith('1'); - }); - - it('calls setFields on unmount', () => { - shallow(); - unmountHandler(); - expect(actions.setFields).toHaveBeenCalledWith([]); - }); - - describe('field values list', () => { - let columns: any; - - const field = { - name: 'Foo', - value: 'Bar', - type: 'string', - }; - - beforeEach(() => { - const wrapper = shallow(); - columns = wrapper.find(EuiBasicTable).props().columns; - }); - - it('will render the field name in the first column', () => { - const column = columns[0]; - const wrapper = shallow(
{column.render(field)}
); - expect(wrapper.text()).toEqual('Foo'); - }); - - it('will render the field value in the second column', () => { - const column = columns[1]; - const wrapper = shallow(
{column.render(field)}
); - expect(wrapper.find(ResultFieldValue).props()).toEqual({ - raw: 'Bar', - type: 'string', - }); - }); - }); - - it('will delete the document when the delete button is pressed', () => { - const wrapper = shallow(); - const button = getPageHeaderActions(wrapper).find('[data-test-subj="DeleteDocumentButton"]'); - - button.simulate('click'); - - expect(actions.deleteDocument).toHaveBeenCalledWith('1'); - }); - - it('hides delete button when the document is a part of a meta engine', () => { - setMockValues({ ...values, isMetaEngine: true }); - const wrapper = shallow(); - - expect( - getPageHeaderActions(wrapper).find('[data-test-subj="DeleteDocumentButton"]') - ).toHaveLength(0); - }); - - it('hides delete button when the document is a part of an elasticsearch-indexed engine', () => { - setMockValues({ ...values, isElasticsearchEngine: true }); - const wrapper = shallow(); - - expect( - getPageHeaderActions(wrapper).find('[data-test-subj="DeleteDocumentButton"]') - ).toHaveLength(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx deleted file mode 100644 index f8e73c28cc24d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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, { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; - -import { useActions, useValues } from 'kea'; - -import { EuiPanel, EuiButton, EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DELETE_BUTTON_LABEL } from '../../../shared/constants'; -import { useDecodedParams } from '../../utils/encode_path_params'; -import { EngineLogic, getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; -import { ResultFieldValue } from '../result'; - -import { DOCUMENTS_TITLE } from './constants'; -import { DocumentDetailLogic } from './document_detail_logic'; -import { FieldDetails } from './types'; - -const DOCUMENT_DETAIL_TITLE = (documentId: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.documentDetail.title', { - defaultMessage: 'Document: {documentId}', - values: { documentId }, - }); - -export const DocumentDetail: React.FC = () => { - const { dataLoading, fields } = useValues(DocumentDetailLogic); - const { deleteDocument, getDocumentDetails, setFields } = useActions(DocumentDetailLogic); - const { isMetaEngine, isElasticsearchEngine } = useValues(EngineLogic); - const showDeleteButton = !isMetaEngine && !isElasticsearchEngine; - - const { documentId } = useParams() as { documentId: string }; - const { documentId: documentTitle } = useDecodedParams(); - - useEffect(() => { - getDocumentDetails(documentId); - return () => { - setFields([]); - }; - }, []); - - const columns: Array> = [ - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.documentDetail.fieldHeader', { - defaultMessage: 'Field', - }), - width: '20%', - render: (field: FieldDetails) => field.name, - }, - { - name: i18n.translate('xpack.enterpriseSearch.appSearch.documentDetail.valueHeader', { - defaultMessage: 'Value', - }), - width: '80%', - render: ({ value, type }: FieldDetails) => , - }, - ]; - - const deleteButton = ( - deleteDocument(documentId)} - data-test-subj="DeleteDocumentButton" - > - {DELETE_BUTTON_LABEL} - - ); - - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts deleted file mode 100644 index 3cdad784a7d27..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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, - mockHttpValues, - mockKibanaValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; -import { mockEngineValues } from '../../__mocks__'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { InternalSchemaType } from '../../../shared/schema/types'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { DocumentDetailLogic } from './document_detail_logic'; - -describe('DocumentDetailLogic', () => { - const { mount } = new LogicMounter(DocumentDetailLogic); - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - dataLoading: true, - fields: [], - }; - - beforeEach(() => { - jest.clearAllMocks(); - mockEngineValues.engineName = 'engine1'; - }); - - describe('actions', () => { - describe('setFields', () => { - it('should set fields to the provided value and dataLoading to false', () => { - const fields = [{ name: 'foo', value: ['foo'], type: InternalSchemaType.String }]; - - mount({ - dataLoading: true, - fields: [], - }); - - DocumentDetailLogic.actions.setFields(fields); - - expect(DocumentDetailLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - fields, - }); - }); - }); - }); - - describe('listeners', () => { - describe('getDocumentDetails', () => { - it('will call an API endpoint and then store the result', async () => { - const fields = [{ name: 'name', value: 'python', type: 'string' }]; - jest.spyOn(DocumentDetailLogic.actions, 'setFields'); - http.get.mockReturnValue(Promise.resolve({ fields })); - - DocumentDetailLogic.actions.getDocumentDetails('1'); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/engine1/documents/1'); - await nextTick(); - expect(DocumentDetailLogic.actions.setFields).toHaveBeenCalledWith(fields); - }); - - it('handles errors', async () => { - mount(); - http.get.mockReturnValue(Promise.reject('An error occurred')); - - DocumentDetailLogic.actions.getDocumentDetails('1'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occurred', { isQueued: true }); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/engine1/documents'); - }); - }); - - describe('deleteDocument', () => { - let confirmSpy: any; - - beforeEach(() => { - confirmSpy = jest.spyOn(window, 'confirm'); - confirmSpy.mockImplementation(jest.fn(() => true)); - http.delete.mockReturnValue(Promise.resolve({})); - }); - - afterEach(() => { - confirmSpy.mockRestore(); - }); - - it('will call an API endpoint and show a success message on the documents page', async () => { - mount(); - DocumentDetailLogic.actions.deleteDocument('1'); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/engine1/documents/1' - ); - await nextTick(); - expect(flashSuccessToast).toHaveBeenCalledWith('Your document was deleted'); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/engine1/documents'); - }); - - it('will do nothing if not confirmed', async () => { - mount(); - window.confirm = () => false; - - DocumentDetailLogic.actions.deleteDocument('1'); - - expect(http.delete).not.toHaveBeenCalled(); - await nextTick(); - }); - - itShowsServerErrorAsFlashMessage(http.delete, () => { - mount(); - DocumentDetailLogic.actions.deleteDocument('1'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts deleted file mode 100644 index 2cc64d7a2becf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; - -import { ENGINE_DOCUMENTS_PATH } from '../../routes'; -import { EngineLogic, generateEnginePath } from '../engine'; - -import { FieldDetails } from './types'; - -interface DocumentDetailLogicValues { - dataLoading: boolean; - fields: FieldDetails[]; -} - -interface DocumentDetailLogicActions { - setFields(fields: FieldDetails[]): { fields: FieldDetails[] }; - deleteDocument(documentId: string): { documentId: string }; - getDocumentDetails(documentId: string): { documentId: string }; -} - -type DocumentDetailLogicType = MakeLogicType; - -export const DocumentDetailLogic = kea({ - path: ['enterprise_search', 'app_search', 'document_detail_logic'], - actions: () => ({ - setFields: (fields) => ({ fields }), - getDocumentDetails: (documentId) => ({ documentId }), - deleteDocument: (documentId) => ({ documentId }), - }), - reducers: () => ({ - dataLoading: [ - true, - { - setFields: () => false, - }, - ], - fields: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setFields: (_, { fields }) => fields, - }, - ], - }), - listeners: ({ actions }) => ({ - getDocumentDetails: async ({ documentId }) => { - const { engineName } = EngineLogic.values; - const { navigateToUrl } = KibanaLogic.values; - - try { - const { http } = HttpLogic.values; - const response = await http.get<{ fields: FieldDetails[] }>( - `/internal/app_search/engines/${engineName}/documents/${documentId}` - ); - actions.setFields(response.fields); - } catch (e) { - // If an error occurs trying to load this document, it will typically be a 404, or some other - // error that will prevent the page from loading, so redirect to the documents page and - // show the error - flashAPIErrors(e, { isQueued: true }); - navigateToUrl(generateEnginePath(ENGINE_DOCUMENTS_PATH)); - } - }, - deleteDocument: async ({ documentId }) => { - const { engineName } = EngineLogic.values; - const { navigateToUrl } = KibanaLogic.values; - - const CONFIRM_DELETE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentDetail.confirmDelete', - { defaultMessage: 'Are you sure you want to delete this document?' } - ); - const DELETE_SUCCESS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentDetail.deleteSuccess', - { defaultMessage: 'Your document was deleted' } - ); - - if (window.confirm(CONFIRM_DELETE)) { - try { - const { http } = HttpLogic.values; - await http.delete(`/internal/app_search/engines/${engineName}/documents/${documentId}`); - flashSuccessToast(DELETE_SUCCESS); - navigateToUrl(generateEnginePath(ENGINE_DOCUMENTS_PATH)); - } catch (e) { - flashAPIErrors(e); - } - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx deleted file mode 100644 index 6c772fdba23d0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { getPageHeaderActions } from '../../../test_helpers'; - -import { DocumentCreationButton } from './components'; -import { SearchExperience } from './search_experience'; - -import { Documents } from '.'; - -describe('Documents', () => { - const values = { - isMetaEngine: false, - myRole: { canManageEngineDocuments: true }, - engine: { elasticsearchIndexName: 'my-elasticsearch-index' }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(SearchExperience).exists()).toBe(true); - }); - - describe('DocumentCreationButton', () => { - it('renders a DocumentCreationButton if the user can manage engine documents', () => { - setMockValues({ - ...values, - myRole: { canManageEngineDocuments: true }, - }); - - const wrapper = shallow(); - expect(getPageHeaderActions(wrapper).find(DocumentCreationButton).exists()).toBe(true); - }); - - it('does not render a DocumentCreationButton if the user cannot manage engine documents', () => { - setMockValues({ - ...values, - myRole: { canManageEngineDocuments: false }, - }); - - const wrapper = shallow(); - expect(getPageHeaderActions(wrapper).find(DocumentCreationButton).exists()).toBe(false); - }); - - it('does not render a DocumentCreationButton for meta engines even if the user can manage engine documents', () => { - setMockValues({ - ...values, - myRole: { canManageEngineDocuments: true }, - isMetaEngine: true, - }); - - const wrapper = shallow(); - expect(getPageHeaderActions(wrapper).find(DocumentCreationButton).exists()).toBe(false); - }); - - it('does not render a DocumentCreationButton for elasticsearch engines even if the user can manage engine documents', () => { - setMockValues({ - ...values, - myRole: { canManageEngineDocuments: true }, - isElasticsearchEngine: true, - }); - - const wrapper = shallow(); - expect(getPageHeaderActions(wrapper).find(DocumentCreationButton).exists()).toBe(false); - }); - }); - - describe('Meta Engines', () => { - it('renders a Meta Engines message if this is a meta engine', () => { - setMockValues({ - ...values, - isMetaEngine: true, - }); - - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="MetaEnginesCallout"]').exists()).toBe(true); - }); - - it('does not render a Meta Engines message if this is not a meta engine', () => { - setMockValues({ - ...values, - isMetaEngine: false, - }); - - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="MetaEnginesCallout"]').exists()).toBe(false); - }); - }); - - describe('Elasticsearch indices', () => { - it('renders an Elasticsearch indices message if this is an Elasticsearch index', () => { - setMockValues({ - ...values, - isElasticsearchEngine: true, - }); - - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ElasticsearchEnginesCallout"]').exists()).toBe(true); - }); - - it('does not render an Elasticsearch indices message if this is not an Elasticsearch index', () => { - setMockValues({ - ...values, - isElasticsearchEngine: false, - }); - - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ElasticsearchEnginesCallout"]').exists()).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx deleted file mode 100644 index 3ef0c192a8f10..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AppLogic } from '../../app_logic'; -import { EngineLogic, getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { DocumentCreationButton, EmptyState } from './components'; -import { DOCUMENTS_TITLE } from './constants'; -import { SearchExperience } from './search_experience'; - -export const Documents: React.FC = () => { - const { - isMetaEngine, - isElasticsearchEngine, - hasNoDocuments, - engine: { elasticsearchIndexName }, - } = useValues(EngineLogic); - const { myRole } = useValues(AppLogic); - const showDocumentCreationButton = - myRole.canManageEngineDocuments && !isMetaEngine && !isElasticsearchEngine; - - return ( - ] : [], - }} - isEmptyState={hasNoDocuments} - emptyState={} - > - {isMetaEngine && ( - <> - -

- {i18n.translate('xpack.enterpriseSearch.appSearch.documents.metaEngineCallout', { - defaultMessage: - 'Meta Engines have many Source Engines. Visit your Source Engines to alter their documents.', - })} -

-
- - - )} - {isElasticsearchEngine && ( - <> - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.elasticsearchEngineCallout', - { - defaultMessage: - "The engine is attached to {elasticsearchIndexName}. You can modify this index's data in Kibana.", - values: { elasticsearchIndexName }, - } - )} -

-
- - - )} - -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts deleted file mode 100644 index 29b2c835f8260..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { DocumentsLogic } from './documents_logic'; - -describe('DocumentsLogic', () => { - const DEFAULT_VALUES = { - isDocumentCreationOpen: false, - }; - - const { mount } = new LogicMounter(DocumentsLogic); - - describe('actions', () => { - describe('openDocumentCreation', () => { - it('should toggle isDocumentCreationOpen to true', () => { - mount({ - isDocumentCreationOpen: false, - }); - - DocumentsLogic.actions.openDocumentCreation(); - - expect(DocumentsLogic.values).toEqual({ - ...DEFAULT_VALUES, - isDocumentCreationOpen: true, - }); - }); - }); - - describe('closeDocumentCreation', () => { - it('should toggle isDocumentCreationOpen to false', () => { - mount({ - isDocumentCreationOpen: true, - }); - - DocumentsLogic.actions.closeDocumentCreation(); - - expect(DocumentsLogic.values).toEqual({ - ...DEFAULT_VALUES, - isDocumentCreationOpen: false, - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts deleted file mode 100644 index 35214dcee5c0c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/documents_logic.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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'; - -interface DocumentsLogicValues { - isDocumentCreationOpen: boolean; -} - -interface DocumentsLogicActions { - closeDocumentCreation(): void; - openDocumentCreation(): void; -} - -type DocumentsLogicType = MakeLogicType; - -export const DocumentsLogic = kea({ - path: ['enterprise_search', 'app_search', 'documents_logic'], - actions: () => ({ - openDocumentCreation: true, - closeDocumentCreation: true, - }), - reducers: () => ({ - isDocumentCreationOpen: [ - false, - { - openDocumentCreation: () => true, - closeDocumentCreation: () => false, - }, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/index.ts deleted file mode 100644 index 07be72767ec60..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { DOCUMENTS_TITLE } from './constants'; -export { DocumentDetailLogic } from './document_detail_logic'; -export { DocumentsLogic } from './documents_logic'; -export { Documents } from './documents'; -export { DocumentDetail } from './document_detail'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/__mocks__/hooks.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/__mocks__/hooks.mock.ts deleted file mode 100644 index fd6478efa4241..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/__mocks__/hooks.mock.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -jest.mock('../hooks', () => ({ - useSearchContextActions: jest.fn(() => ({})), - useSearchContextState: jest.fn(() => ({})), -})); - -import { useSearchContextState, useSearchContextActions } from '../hooks'; - -export const setMockSearchContextState = (values: object) => { - (useSearchContextState as jest.Mock).mockImplementation(() => values); -}; -export const setMockSearchContextActions = (actions: object) => { - (useSearchContextActions as jest.Mock).mockImplementation(() => actions); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts deleted file mode 100644 index 919dcdb354980..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 type { APIConnector } from '@elastic/search-ui'; - -import { SchemaType } from '../../../../shared/schema/types'; - -import { buildSearchUIConfig } from './build_search_ui_config'; - -describe('buildSearchUIConfig', () => { - it('builds a configuration object for Search UI', () => { - const connector = {}; - const schema = { - foo: { - type: SchemaType.Text, - capabilities: { - snippet: true, - facet: true, - }, - }, - bar: { - type: SchemaType.Number, - capabilities: { - snippet: false, - facet: false, - }, - }, - }; - const fields = { - filterFields: ['foo', 'bar'], - sortFields: [], - }; - - const config = buildSearchUIConfig(connector as APIConnector, schema, fields); - expect(config).toEqual({ - alwaysSearchOnInitialLoad: true, - apiConnector: connector, - initialState: { - sortDirection: 'desc', - sortField: 'id', - }, - searchQuery: { - disjunctiveFacets: ['foo'], - facets: { - foo: { - size: 30, - type: 'value', - }, - }, - result_fields: { - bar: { - raw: {}, - }, - foo: { - raw: {}, - snippet: { - size: 300, - }, - }, - }, - }, - trackUrlState: false, - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts deleted file mode 100644 index 97a8956dee75b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 type { APIConnector, SortDirection } from '@elastic/search-ui'; - -import { SchemaType, AdvancedSchema } from '../../../../shared/schema/types'; - -import { Fields } from './types'; - -export const buildSearchUIConfig = ( - apiConnector: APIConnector, - schema: AdvancedSchema, - fields: Fields, - initialState = { sortDirection: 'desc' as SortDirection, sortField: 'id' } -) => { - const facets = fields.filterFields - .filter((fieldName) => !!schema[fieldName] && schema[fieldName].type !== SchemaType.Geolocation) - .filter((fieldName) => !!schema[fieldName].capabilities.facet) - .reduce((facetsConfig, fieldName) => { - return { - ...facetsConfig, - [fieldName]: { type: 'value', size: 30 }, - }; - }, {}); - - const resultFields = Object.entries(schema) - .filter(([, schemaField]) => schemaField.type !== SchemaType.Nested) - .reduce((acc, [fieldName, schemaField]) => { - if (schemaField.capabilities.snippet) { - return { ...acc, [fieldName]: { raw: {}, snippet: { size: 300 } } }; - } - return { ...acc, [fieldName]: { raw: {} } }; - }, {}); - - return { - alwaysSearchOnInitialLoad: true, - apiConnector, - trackUrlState: false, - initialState, - searchQuery: { - disjunctiveFacets: Object.keys(facets), - facets, - result_fields: resultFields, - }, - }; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts deleted file mode 100644 index efe11a5d39090..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 { buildSortOptions } from './build_sort_options'; - -describe('buildSortOptions', () => { - it('builds sort options from a list of field names', () => { - const sortOptions = buildSortOptions( - { - filterFields: [], - sortFields: ['fieldA', 'fieldB'], - }, - [ - { - name: 'Relevance (asc)', - value: 'id', - direction: 'desc', - }, - { - name: 'Relevance (desc)', - value: 'id', - direction: 'asc', - }, - ] - ); - - expect(sortOptions).toEqual([ - { - name: 'Relevance (asc)', - value: 'id', - direction: 'desc', - }, - { - name: 'Relevance (desc)', - value: 'id', - direction: 'asc', - }, - { - direction: 'asc', - name: 'fieldA (asc)', - value: 'fieldA', - }, - { - direction: 'desc', - name: 'fieldA (desc)', - value: 'fieldA', - }, - { - direction: 'asc', - name: 'fieldB (asc)', - value: 'fieldB', - }, - { - direction: 'desc', - name: 'fieldB (desc)', - value: 'fieldB', - }, - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts deleted file mode 100644 index 54cf2bdd4f257..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_sort_options.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { flatten } from 'lodash'; - -import { ASCENDING, DESCENDING } from './constants'; -import { Fields, SortOption, SortDirection } from './types'; - -const fieldNameToSortOptions = (fieldName: string): SortOption[] => - ['asc', 'desc'].map((direction) => ({ - name: direction === 'asc' ? ASCENDING(fieldName) : DESCENDING(fieldName), - value: fieldName, - direction: direction as SortDirection, - })); - -/** - * Adds two sort options for a given field, a "desc" and an "asc" option. - */ -export const buildSortOptions = ( - fields: Fields, - defaultSortOptions: SortOption[] -): SortOption[] => { - const sortFieldsOptions = flatten(fields.sortFields.map(fieldNameToSortOptions)); - const sortingOptions = [...defaultSortOptions, ...sortFieldsOptions]; - return sortingOptions; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts deleted file mode 100644 index c02f33cccd253..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const ASCENDING = (fieldName: string) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.ascendingDropDownOptionLabel', - { - defaultMessage: '{fieldName} (asc)', - values: { fieldName }, - } - ); - -export const DESCENDING = (fieldName: string) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.descendingDropDownOptionLabel', - { - defaultMessage: '{fieldName} (desc)', - values: { fieldName }, - } - ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_callout.test.tsx deleted file mode 100644 index 6ed2d7edc9639..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_callout.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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, ShallowWrapper } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { CustomizationCallout } from './customization_callout'; - -describe('CustomizationCallout', () => { - let wrapper: ShallowWrapper; - const onClick = jest.fn(); - - beforeAll(() => { - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('calls onClick param when the Customize button is clicked', () => { - wrapper.find(EuiButton).simulate('click'); - expect(onClick).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_callout.tsx deleted file mode 100644 index 48a9fcdeaa878..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_callout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { EuiButton, EuiFlexGroup, EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -interface Props { - onClick(): void; -} - -export const CustomizationCallout: React.FC = ({ onClick }) => { - return ( - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.message', - { - defaultMessage: - 'Did you know that you can customize your document search experience? Click "Customize" below to get started.', - } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationCallout.button', - { - defaultMessage: 'Customize', - } - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.test.tsx deleted file mode 100644 index 536698ffe28f3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; - -import { CustomizationModal } from './customization_modal'; - -describe('CustomizationModal', () => { - const props = { - filterFields: ['field1', 'field2'], - sortFields: ['sortField1', 'sortField2'], - onClose: jest.fn(), - onSave: jest.fn(), - }; - - const values = { - engine: { - name: 'some-engine', - apiKey: '1234', - }, - }; - - const actions = { - setEngine: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('when save is clicked, it calls onSave prop with selected filter and sort fields', () => { - const wrapper = shallow(); - wrapper.find(EuiButton).simulate('click'); - expect(props.onSave).toHaveBeenCalledWith({ - filterFields: ['field1', 'field2'], - sortFields: ['sortField1', 'sortField2'], - }); - }); - - it('when save is clicked, it calls onSave prop when with updated selections', () => { - const wrapper = shallow(); - - const sortFieldsDropdown = wrapper.find('[data-test-subj="sortFieldsDropdown"]'); - sortFieldsDropdown.simulate('change', [{ label: 'newSort1' }]); - - const filterFieldsDropdown = wrapper.find('[data-test-subj="filterFieldsDropdown"]'); - filterFieldsDropdown.simulate('change', [{ label: 'newField1' }]); - - wrapper.find(EuiButton).simulate('click'); - expect(props.onSave).toHaveBeenCalledWith({ - filterFields: ['newField1'], - sortFields: ['newSort1'], - }); - }); - - it('calls onClose when cancel is clicked', () => { - const wrapper = shallow(); - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(props.onClose).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx deleted file mode 100644 index 779158ae1e114..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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, { useState, useMemo } from 'react'; - -import { useValues } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiComboBox, - EuiForm, - EuiFormRow, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - useGeneratedHtmlId, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SAVE_BUTTON_LABEL, CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; - -import { EngineLogic } from '../../engine'; - -interface Props { - filterFields: string[]; - sortFields: string[]; - onClose(): void; - onSave({ sortFields, filterFields }: { sortFields: string[]; filterFields: string[] }): void; -} - -const fieldNameToComboBoxOption = (fieldName: string) => ({ label: fieldName }); -const comboBoxOptionToFieldName = ({ label }: { label: string }) => label; - -export const CustomizationModal: React.FC = ({ - filterFields, - onClose, - onSave, - sortFields, -}) => { - const { engine } = useValues(EngineLogic); - const modalTitleId = useGeneratedHtmlId(); - - const [selectedFilterFields, setSelectedFilterFields] = useState( - filterFields.map(fieldNameToComboBoxOption) - ); - const [selectedSortFields, setSelectedSortFields] = useState( - sortFields.map(fieldNameToComboBoxOption) - ); - - const schema = engine.advancedSchema || {}; - const selectableFilterFields = useMemo( - () => - Object.keys(schema) - .filter((fieldName) => schema[fieldName].capabilities.filter) - .map(fieldNameToComboBoxOption), - [schema] - ); - const selectableSortFields = useMemo( - () => - Object.keys(schema) - .filter((fieldName) => schema[fieldName].capabilities.sort) - .map(fieldNameToComboBoxOption), - [schema] - ); - - return ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.title', - { - defaultMessage: 'Customize document search', - } - )} - - - - - - - - - - - - - - - {CANCEL_BUTTON_LABEL} - - { - onSave({ - filterFields: selectedFilterFields.map(comboBoxOptionToFieldName), - sortFields: selectedSortFields.map(comboBoxOptionToFieldName), - }); - }} - > - {SAVE_BUTTON_LABEL} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx deleted file mode 100644 index f1bf6289929a2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - */ - -const mockAction = jest.fn(); - -let mockSubcription: (state: object) => void; -const mockDriver = { - state: { searchTerm: 'foo' }, - actions: { setSearchTerm: mockAction }, - subscribeToStateChanges: jest.fn().mockImplementation((fn) => { - mockSubcription = fn; - }), - unsubscribeToStateChanges: jest.fn(), -}; - -jest.mock('react', () => ({ - ...(jest.requireActual('react') as object), - useContext: jest.fn(() => ({ - driver: mockDriver, - })), -})); - -import React from 'react'; - -import { mount, ReactWrapper } from 'enzyme'; -import { act } from 'react-dom/test-utils'; - -import { useSearchContextState, useSearchContextActions } from './hooks'; - -describe('hooks', () => { - describe('useSearchContextState', () => { - const TestComponent = () => { - const { searchTerm } = useSearchContextState(); - return
{searchTerm}
; - }; - - let wrapper: ReactWrapper; - beforeAll(() => { - wrapper = mount(); - }); - - it('exposes search state', () => { - expect(wrapper.text()).toEqual('foo'); - }); - - it('subscribes to state changes', () => { - act(() => { - mockSubcription({ searchTerm: 'bar' }); - }); - - expect(wrapper.text()).toEqual('bar'); - }); - - it('unsubscribes to state changes when unmounted', () => { - wrapper.unmount(); - - expect(mockDriver.unsubscribeToStateChanges).toHaveBeenCalled(); - }); - }); - - describe('useSearchContextActions', () => { - it('exposes actions', () => { - const TestComponent = () => { - const { setSearchTerm } = useSearchContextActions(); - setSearchTerm('bar'); - return null; - }; - - mount(); - expect(mockAction).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts deleted file mode 100644 index 7b1029970a444..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 { useContext, useEffect, useState } from 'react'; - -import { SearchContext } from '@elastic/react-search-ui'; -import type { SearchState } from '@elastic/search-ui'; - -export const useSearchContextState = () => { - const { driver } = useContext(SearchContext); - const [state, setState] = useState(driver.state); - - useEffect(() => { - const subscription = (newState: SearchState) => { - setState(newState); - }; - driver.subscribeToStateChanges(subscription); - return () => { - driver.unsubscribeToStateChanges(subscription); - }; - }, [state]); - - return state; -}; - -export const useSearchContextActions = () => { - const { driver } = useContext(SearchContext); - return driver.actions; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/index.ts deleted file mode 100644 index 91ef7df709099..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SearchExperience } from './search_experience'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx deleted file mode 100644 index cc438abd9ba94..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { Paging, ResultsPerPage } from '@elastic/react-search-ui'; - -import { Pagination } from './pagination'; - -describe('Pagination', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(Paging).exists()).toBe(true); - expect(wrapper.find(ResultsPerPage).exists()).toBe(true); - }); - - it('passes aria-label through to Paging', () => { - const wrapper = shallow(); - expect(wrapper.find(Paging).prop('aria-label')).toEqual('foo'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx deleted file mode 100644 index 53b0e22553d52..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Paging, ResultsPerPage } from '@elastic/react-search-ui'; - -import { PagingView, ResultsPerPageView } from './views'; - -export const Pagination: React.FC<{ 'aria-label': string }> = ({ 'aria-label': ariaLabel }) => ( - - - {/* @ts-ignore */} - - - - - - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss deleted file mode 100644 index 34aac402fbb39..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.scss +++ /dev/null @@ -1,37 +0,0 @@ -.documentsSearchExperience { - .sui-results-container { - flex-grow: 1; - padding: 0; - - > li + li { - margin-top: $euiSize; - } - } - - .documentsSearchExperience__sidebar { - flex-grow: 1; - min-width: $euiSize * 19; - } - - .documentsSearchExperience__content { - flex-grow: 4; - position: relative; - } - - .documentsSearchExperience__pagingInfo { - flex-grow: 0; - } - - .customizationCallout { - background-color: $euiPageBackgroundColor; - padding: $euiSizeL; - } - - .documentsSearchExperience__facet { - line-height: 0; - - .euiCheckbox__label { - @include euiTextTruncate; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx deleted file mode 100644 index 1a6a2abba0904..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 '../../../../__mocks__/enterprise_search_url.mock'; -import { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { SearchProvider, Facet } from '@elastic/react-search-ui'; - -jest.mock('../../../../shared/use_local_storage', () => ({ - useLocalStorage: jest.fn(), -})); -import { useLocalStorage } from '../../../../shared/use_local_storage'; - -import { CustomizationCallout } from './customization_callout'; -import { CustomizationModal } from './customization_modal'; -import { SearchExperienceContent } from './search_experience_content'; -import { Fields } from './types'; - -import { SearchExperience } from '.'; - -describe('SearchExperience', () => { - const values = { - engine: { - name: 'some-engine', - apiKey: '1234', - document_count: 50, - }, - }; - const mockSetFields = jest.fn(); - const setFieldsInLocalStorage = (fields: Fields) => { - (useLocalStorage as jest.Mock).mockImplementation(() => [fields, mockSetFields]); - }; - - beforeEach(() => { - setFieldsInLocalStorage({ - filterFields: ['a', 'b', 'c'], - sortFields: ['d', 'c'], - }); - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(SearchProvider)).toHaveLength(1); - expect(wrapper.find(SearchExperienceContent)).toHaveLength(1); - }); - - describe('when there are no selected filter fields', () => { - let wrapper: ShallowWrapper; - beforeEach(() => { - setFieldsInLocalStorage({ - filterFields: [], - sortFields: ['a', 'b'], - }); - wrapper = shallow(); - }); - - it('shows a customize callout instead of a button if no fields are yet selected', () => { - expect(wrapper.find(CustomizationCallout).exists()).toBe(true); - expect(wrapper.find('[data-test-subj="customize"]').exists()).toBe(false); - }); - - it('will show the customization modal when clicked', () => { - expect(wrapper.find(CustomizationModal).exists()).toBe(false); - wrapper.find(CustomizationCallout).simulate('click'); - - expect(wrapper.find(CustomizationModal).exists()).toBe(true); - }); - }); - - describe('when there are selected filter fields', () => { - let wrapper: ShallowWrapper; - beforeEach(() => { - setFieldsInLocalStorage({ - filterFields: ['a', 'b'], - sortFields: ['a', 'b'], - }); - wrapper = shallow(); - }); - - it('shows a customize button', () => { - expect(wrapper.find(CustomizationCallout).exists()).toBe(false); - expect(wrapper.find('[data-test-subj="customize"]').exists()).toBe(true); - }); - }); - - it('renders Facet components for filter fields', () => { - setFieldsInLocalStorage({ - filterFields: ['a', 'b', 'c'], - sortFields: [], - }); - const wrapper = shallow(); - expect(wrapper.find(Facet).length).toBe(3); - }); - - describe('customization modal', () => { - it('has a customization modal which can be opened and closed', () => { - const wrapper = shallow(); - expect(wrapper.find(CustomizationModal).exists()).toBe(false); - - wrapper.find('[data-test-subj="customize"]').simulate('click'); - expect(wrapper.find(CustomizationModal).exists()).toBe(true); - - wrapper.find(CustomizationModal).prop('onClose')(); - expect(wrapper.find(CustomizationModal).exists()).toBe(false); - }); - - it('passes values from localStorage to the customization modal', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="customize"]').simulate('click'); - expect(wrapper.find(CustomizationModal).prop('filterFields')).toEqual(['a', 'b', 'c']); - expect(wrapper.find(CustomizationModal).prop('sortFields')).toEqual(['d', 'c']); - }); - - it('updates selected fields in localStorage and closes modal on save', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="customize"]').simulate('click'); - wrapper.find(CustomizationModal).prop('onSave')({ - filterFields: ['new', 'filters'], - sortFields: ['new', 'sorts'], - }); - - expect(mockSetFields).toHaveBeenCalledWith({ - filterFields: ['new', 'filters'], - sortFields: ['new', 'sorts'], - }); - - expect(wrapper.find(CustomizationModal).exists()).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx deleted file mode 100644 index 441841364489c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { useValues } from 'kea'; - -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { SearchProvider, SearchBox, Sorting, Facet } from '@elastic/react-search-ui'; -import type { SortDirection } from '@elastic/search-ui'; -import AppSearchAPIConnector from '@elastic/search-ui-app-search-connector'; -import { i18n } from '@kbn/i18n'; - -import './search_experience.scss'; - -import { HttpLogic } from '../../../../shared/http'; -import { useLocalStorage } from '../../../../shared/use_local_storage'; -import { EngineLogic } from '../../engine'; - -import { buildSearchUIConfig } from './build_search_ui_config'; -import { buildSortOptions } from './build_sort_options'; -import { ASCENDING, DESCENDING } from './constants'; -import { CustomizationCallout } from './customization_callout'; -import { CustomizationModal } from './customization_modal'; -import { SearchExperienceContent } from './search_experience_content'; -import { Fields, SortOption } from './types'; -import { SearchBoxView, SortingView, MultiCheckboxFacetsView } from './views'; - -const DOCUMENT_ID = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.documentId', - { - defaultMessage: 'Document ID', - } -); - -const RELEVANCE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.option.relevance', - { defaultMessage: 'Relevance' } -); - -const DEFAULT_SORT_OPTIONS: SortOption[] = [ - { - name: DESCENDING(DOCUMENT_ID), - value: 'id', - direction: 'desc', - }, - { - name: ASCENDING(DOCUMENT_ID), - value: 'id', - direction: 'asc', - }, -]; - -const RELEVANCE_SORT_OPTIONS: SortOption[] = [ - { - name: RELEVANCE, - value: '_score', - direction: 'desc', - }, -]; - -export const SearchExperience: React.FC = () => { - const { engine } = useValues(EngineLogic); - const { http } = useValues(HttpLogic); - const endpointBase = http.basePath.prepend('/internal/app_search/search-ui'); - - const [showCustomizationModal, setShowCustomizationModal] = useState(false); - const openCustomizationModal = () => setShowCustomizationModal(true); - const closeCustomizationModal = () => setShowCustomizationModal(false); - - const [fields, setFields] = useLocalStorage( - `documents-search-experience-customization--${engine.name}`, - { - filterFields: [], - sortFields: [], - } - ); - const useRelevance = engine.type === 'elasticsearch' || engine.type === 'meta'; - const sortOptions = useRelevance ? RELEVANCE_SORT_OPTIONS : DEFAULT_SORT_OPTIONS; - - const sortingOptions = buildSortOptions(fields, sortOptions); - - const connector = new AppSearchAPIConnector({ - cacheResponses: false, - endpointBase, - engineName: engine.name as string, - additionalHeaders: { - 'kbn-xsrf': true, - }, - } as ConstructorParameters[0]); - - const initialState = { - sortField: sortOptions[0].value, - sortDirection: 'desc' as SortDirection, - }; - - const searchProviderConfig = buildSearchUIConfig( - connector, - engine.advancedSchema || {}, - fields, - initialState - ); - - return ( -
- - - - - - - - {fields.filterFields.length > 0 ? ( - <> - {fields.filterFields.map((fieldName) => ( -
- - -
- ))} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationButton', - { - defaultMessage: 'Customize filters and sort', - } - )} - - - ) : ( - - )} -
- - - -
-
- {showCustomizationModal && ( - { - setFields({ filterFields, sortFields }); - closeCustomizationModal(); - }} - /> - )} -
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx deleted file mode 100644 index 53570e8868f44..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 { setMockSearchContextState } from './__mocks__/hooks.mock'; -import { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { Results } from '@elastic/react-search-ui'; - -import { Loading } from '../../../../shared/loading'; -import { SchemaType } from '../../../../shared/schema/types'; - -import { Pagination } from './pagination'; -import { SearchExperienceContent } from './search_experience_content'; -import { ResultView } from './views'; - -describe('SearchExperienceContent', () => { - const searchState = { - resultSearchTerm: 'searchTerm', - totalResults: 100, - wasSearched: true, - }; - const values = { - engineName: 'engine1', - isMetaEngine: false, - myRole: { canManageEngineDocuments: true }, - engine: { - schema: { - title: SchemaType.Text, - }, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockSearchContextState(searchState); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('passes result, schema, and isMetaEngine to the result view', () => { - const result = { - id: { - raw: '1', - }, - _meta: { - id: '1', - score: 100, - engine: 'my-engine', - }, - foo: { - raw: 'bar', - }, - }; - - const wrapper = shallow(); - const resultView: any = wrapper.find(Results).prop('resultView'); - expect(resultView({ result })).toEqual( - - ); - }); - - it('renders pagination', () => { - const wrapper = shallow(); - expect(wrapper.find(Pagination).exists()).toBe(true); - }); - - it('renders a loading state if a search was not performed yet', () => { - setMockSearchContextState({ - ...searchState, - wasSearched: false, - }); - const wrapper = shallow(); - expect(wrapper.find(Loading)).toHaveLength(1); - }); - - it('renders results if a search was performed and there are more than 0 totalResults', () => { - setMockSearchContextState({ - ...searchState, - wasSearched: true, - totalResults: 10, - }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="documentsSearchResults"]').length).toBe(1); - }); - - it('renders a no results message if a non-empty search was performed and there are no results', () => { - setMockSearchContextState({ - ...searchState, - resultSearchTerm: 'searchTerm', - wasSearched: true, - totalResults: 0, - }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="documentsSearchResults"]').length).toBe(0); - expect(wrapper.find('[data-test-subj="documentsSearchNoResults"]').length).toBe(1); - }); - - it('renders empty if an empty search was performed and there are no results', () => { - // In a real world scenario this does not happen - wasSearched returns false before this final branch - setMockSearchContextState({ - ...searchState, - resultSearchTerm: '', - totalResults: 0, - wasSearched: true, - }); - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx deleted file mode 100644 index 2dbafaacb1e93..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiFlexGroup, EuiSpacer, EuiEmptyPrompt } from '@elastic/eui'; - -import { Results } from '@elastic/react-search-ui'; -import type { SearchResult } from '@elastic/search-ui'; -import { i18n } from '@kbn/i18n'; - -import { Loading } from '../../../../shared/loading'; -import { EngineLogic } from '../../engine'; - -import { useSearchContextState } from './hooks'; -import { Pagination } from './pagination'; -import { ResultView } from './views'; - -export const SearchExperienceContent: React.FC = () => { - const { resultSearchTerm, totalResults, wasSearched } = useSearchContextState(); - - const { isMetaEngine, engine } = useValues(EngineLogic); - - if (!wasSearched) return ; - - if (totalResults) { - return ( - - - - { - return ( - - ); - }} - /> - - - - ); - } - - // If we have no results, but have a search term, show a message - if (resultSearchTerm) { - return ( - - ); - } - - return null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts deleted file mode 100644 index ca3e6cd48dc5f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 interface Fields { - filterFields: string[]; - sortFields: string[]; -} - -export type SortDirection = 'asc' | 'desc'; - -export interface SortOption { - name: string; - value: string; - direction: SortDirection; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts deleted file mode 100644 index 2c756213293e0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 { SearchBoxView } from './search_box_view'; -export { SortingView } from './sorting_view'; -export { ResultView } from './result_view'; -export { ResultsPerPageView } from './results_per_page_view'; -export { PagingView } from './paging_view'; -export { MultiCheckboxFacetsView } from './multi_checkbox_facets_view'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx deleted file mode 100644 index 85984054ebd21..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 type { FacetValue } from '@elastic/search-ui'; - -import { MultiCheckboxFacetsView } from './multi_checkbox_facets_view'; - -describe('MultiCheckboxFacetsView', () => { - const props = { - label: 'foo', - options: [ - { - value: 'value1', - selected: false, - }, - { - value: 'value2', - selected: false, - }, - ] as FacetValue[], - showMore: true, - onMoreClick: jest.fn(), - onRemove: jest.fn(), - onSelect: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('calls onMoreClick when more button is clicked', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="more"]').simulate('click'); - expect(props.onMoreClick).toHaveBeenCalled(); - }); - - it('calls onSelect when an option is selected', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="checkbox-group"]').simulate('change', 'generated-id_1'); - expect(props.onSelect).toHaveBeenCalledWith('value2'); - }); - - it('calls onRemove if the option was already selected', () => { - const wrapper = shallow( - - ); - wrapper.find('[data-test-subj="checkbox-group"]').simulate('change', 'generated-id_1'); - expect(props.onRemove).toHaveBeenCalledWith('value2'); - }); - - it('it passes options to EuiCheckboxGroup, converting no values to the text "No Value"', () => { - const wrapper = shallow( - - ); - const options = wrapper.find('[data-test-subj="checkbox-group"]').prop('options'); - expect(options).toEqual([ - { id: 'generated-id_0', label: 'value1' }, - { id: 'generated-id_1', label: '' }, - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx deleted file mode 100644 index d4308c215072c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 { - htmlIdGenerator, - EuiCheckboxGroup, - EuiFlexGroup, - EuiButtonEmpty, - EuiSpacer, -} from '@elastic/eui'; -import type { FieldValue, FacetValue } from '@elastic/search-ui'; -import { i18n } from '@kbn/i18n'; - -interface Props { - label: string; - options: FacetValue[]; - showMore: boolean; - onMoreClick(): void; - onRemove(value: FieldValue): void; - onSelect(value: FieldValue): void; -} - -const getIndexFromId = (id: string) => parseInt(id.split('_')[1], 10); - -export const MultiCheckboxFacetsView: React.FC = ({ - label, - onMoreClick, - onRemove, - onSelect, - options, - showMore, -}) => { - const getId = htmlIdGenerator(); - - const optionToCheckBoxGroupOption = (option: FacetValue, index: number) => ({ - id: getId(String(index)), - label: - (option.value as React.ReactNode) || - i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.noValue.selectOption', - { - defaultMessage: '', - ignoreTag: true, - } - ), - }); - - const optionToSelectedMapReducer = ( - selectedMap: { [name: string]: boolean }, - option: FacetValue, - index: number - ) => { - if (option.selected) { - selectedMap[getId(String(index))] = true; - } - return selectedMap; - }; - - const checkboxGroupOptions = options.map(optionToCheckBoxGroupOption); - const idToSelectedMap = options.reduce(optionToSelectedMapReducer, {}); - - const onChange = (checkboxId: string) => { - const index = getIndexFromId(checkboxId); - const option = options[index]; - if (option.selected) { - onRemove(option.value as FieldValue); - return; - } - onSelect(option.value as FieldValue); - }; - - return ( - <> - - {showMore && ( - <> - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.multiCheckboxFacetsView.showMore', - { - defaultMessage: 'Show more', - } - )} - - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.test.tsx deleted file mode 100644 index 28cd126e5c004..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 { EuiPagination } from '@elastic/eui'; - -import { PagingView } from './paging_view'; - -describe('PagingView', () => { - const props = { - current: 1, - totalPages: 20, - onChange: jest.fn(), - 'aria-label': 'paging view', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPagination).length).toBe(1); - }); - - it('passes through totalPage', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPagination).prop('pageCount')).toEqual(20); - }); - - it('passes through aria-label', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPagination).prop('aria-label')).toEqual('paging view'); - }); - - it('decrements current page by 1 and passes it through as activePage', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPagination).prop('activePage')).toEqual(0); - }); - - it('calls onChange when onPageClick is triggered, and adds 1', () => { - const wrapper = shallow(); - const onPageClick: any = wrapper.find(EuiPagination).prop('onPageClick'); - onPageClick(3); - expect(props.onChange).toHaveBeenCalledWith(4); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx deleted file mode 100644 index c79ea36ba100a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { EuiPagination } from '@elastic/eui'; - -interface Props { - current?: number; - totalPages: number; - onChange(pageNumber: number): void; - 'aria-label': string; -} - -export const PagingView: React.FC = ({ - current = 1, - onChange, - totalPages, - 'aria-label': ariaLabel, -}) => ( - onChange(page + 1)} // EuiPagination is 0-indexed, Search UI is 1-indexed - aria-label={ariaLabel} - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx deleted file mode 100644 index 0e7c3c57c41ea..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { SchemaType } from '../../../../../shared/schema/types'; -import { Result } from '../../../result/result'; - -import { ResultView } from '.'; - -describe('ResultView', () => { - const result = { - id: { - raw: '1', - }, - title: { - raw: 'A title', - }, - _meta: { - id: '1', - score: 100, - engine: 'my-engine', - }, - }; - - const schema = { - title: SchemaType.Text, - }; - - it('renders', () => { - const wrapper = shallow( - - ); - expect(wrapper.find(Result).props()).toEqual({ - result, - shouldLinkToDetailPage: true, - schemaForTypeHighlights: schema, - isMetaEngine: true, - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx deleted file mode 100644 index 930b60f22a947..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 type { SearchResult } from '@elastic/search-ui'; - -import { Schema } from '../../../../../shared/schema/types'; -import { Result } from '../../../result/result'; - -export interface Props { - result: SearchResult; - schemaForTypeHighlights?: Schema; - isMetaEngine: boolean; -} - -export const ResultView: React.FC = ({ result, schemaForTypeHighlights, isMetaEngine }) => { - return ( -
  • - -
  • - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.test.tsx deleted file mode 100644 index 24db762e26e32..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { EuiSelect } from '@elastic/eui'; - -import { ResultsPerPageView } from '.'; - -describe('ResultsPerPageView', () => { - const props = { - options: [1, 2, 3], - value: 1, - onChange: jest.fn(), - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiSelect).length).toBe(1); - }); - - it('maps options to correct EuiSelect option', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiSelect).prop('options')).toEqual([ - { text: 1, value: 1 }, - { text: 2, value: 2 }, - { text: 3, value: 3 }, - ]); - }); - - it('passes through the value if it exists in options', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiSelect).prop('value')).toEqual(1); - }); - - it('does not pass through the value if it does not exist in options', () => { - const wrapper = shallow( - - ); - expect(wrapper.find(EuiSelect).prop('value')).toBeUndefined(); - }); - - it('passes through an onChange to EuiSelect', () => { - const wrapper = shallow(); - const onChange: any = wrapper.find(EuiSelect).prop('onChange'); - onChange({ target: { value: 2 } }); - expect(props.onChange).toHaveBeenCalledWith(2); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx deleted file mode 100644 index 63050ca1d79ff..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { EuiSelect, EuiSelectOption } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -const wrapResultsPerPageOptionForEuiSelect: (option: number) => EuiSelectOption = (option) => ({ - text: option, - value: option, -}); - -interface Props { - options?: number[]; - value?: number; - onChange(value: number): void; -} - -export const ResultsPerPageView: React.FC = ({ onChange, options = [], value = 20 }) => { - // If we don't have the value in options, unset it - const selectedValue = value && !options.includes(value) ? undefined : value; - - return ( -
    - onChange(parseInt(event.target.value, 10))} - aria-label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.resultsPerPage.ariaLabel', - { - defaultMessage: 'Number of results to show per page', - } - )} - /> -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/search_box_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/search_box_view.test.tsx deleted file mode 100644 index a35fcefb30ac6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/search_box_view.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { EuiFieldSearch } from '@elastic/eui'; - -import { SearchBoxView } from './search_box_view'; - -describe('SearchBoxView', () => { - const props = { - onChange: jest.fn(), - value: 'foo', - inputProps: { - placeholder: 'bar', - 'aria-label': 'foo', - 'data-test-subj': 'bar', - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.type()).toEqual(EuiFieldSearch); - expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('foo'); - expect(wrapper.find(EuiFieldSearch).prop('placeholder')).toEqual('bar'); - }); - - it('passes through an onChange to EuiFieldSearch', () => { - const wrapper = shallow(); - wrapper.prop('onChange')({ target: { value: 'test' } }); - expect(props.onChange).toHaveBeenCalledWith('test'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/search_box_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/search_box_view.tsx deleted file mode 100644 index f593eb647e52b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/search_box_view.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { EuiFieldSearch } from '@elastic/eui'; - -interface Props { - inputProps: { - placeholder: string; - 'aria-label': string; - 'data-test-subj': string; - }; - value: string; - onChange(value: string): void; -} - -export const SearchBoxView: React.FC = ({ onChange, value, inputProps }) => { - return ( - onChange(event.target.value)} - fullWidth - {...inputProps} - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/sorting_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/sorting_view.test.tsx deleted file mode 100644 index a147f45feef14..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/sorting_view.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { EuiSelect } from '@elastic/eui'; - -import { SortingView } from '.'; - -describe('SortingView', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - const props = { - options: [{ label: 'Label', value: 'Value' }], - value: 'Value', - onChange: jest.fn(), - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiSelect).length).toBe(1); - }); - - it('maps options to correct EuiSelect option', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiSelect).prop('options')).toEqual([{ text: 'Label', value: 'Value' }]); - }); - - it('passes through the value if it exists in options', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiSelect).prop('value')).toEqual('Value'); - }); - - it('does not pass through the value if it does not exist in options', () => { - const wrapper = shallow( - - ); - expect(wrapper.find(EuiSelect).prop('value')).toBeUndefined(); - }); - - it('passes through an onChange to EuiSelect', () => { - const wrapper = shallow(); - const onChange: any = wrapper.find(EuiSelect).prop('onChange'); - onChange({ target: { value: 'test' } }); - expect(props.onChange).toHaveBeenCalledWith('test'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/sorting_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/sorting_view.tsx deleted file mode 100644 index e3f21b67a6530..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/sorting_view.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { EuiSelect, EuiSelectOption } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -interface Option { - label: string; - value: string; -} - -const wrapSortingOptionForEuiSelect: (option: Option) => EuiSelectOption = (option) => ({ - text: option.label, - value: option.value, -}); - -const getValueFromOption: (option: Option) => string = (option) => option.value; - -interface Props { - options: Option[]; - value: string; - onChange(value: string): void; -} - -export const SortingView: React.FC = ({ onChange, options, value }) => { - // If we don't have the value in options, unset it - const valuesFromOptions = options.map(getValueFromOption); - const selectedValue = value && !valuesFromOptions.includes(value) ? undefined : value; - - return ( - <> - onChange(event.target.value)} - aria-label={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.sortBy.ariaLabel', - { - defaultMessage: 'Sort results by', - } - )} - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.ts deleted file mode 100644 index a6b4a307b9c5f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 { InternalSchemaType } from '../../../shared/schema/types'; - -export interface FieldDetails { - name: string; - value: string | string[]; - type: InternalSchemaType; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts deleted file mode 100644 index 9102f706fdbed..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const POLLING_DURATION = 5000; - -export const POLLING_ERROR_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.pollingErrorMessage', - { defaultMessage: 'Could not fetch engine data' } -); - -export const POLLING_ERROR_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.pollingErrorDescription', - { defaultMessage: 'Please check your connection or manually reload the page.' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts deleted file mode 100644 index 2dfb7945a0d46..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { SchemaType } from '../../../shared/schema/types'; -import { ApiTokenTypes } from '../credentials/constants'; - -import { EngineTypes } from './types'; - -import { EngineLogic } from '.'; - -describe('EngineLogic', () => { - const { mount, unmount } = new LogicMounter(EngineLogic); - const { http } = mockHttpValues; - const { flashErrorToast } = mockFlashMessageHelpers; - - const mockEngineData = { - name: 'some-engine', - type: EngineTypes.default, - created_at: 'some date timestamp', - language: null, - document_count: 1, - field_count: 1, - result_fields: { - id: { raw: {} }, - }, - unconfirmedFields: [], - unsearchedUnconfirmedFields: false, - sample: false, - isMeta: false, - invalidBoosts: false, - schema: { test: SchemaType.Text }, - advancedSchema: { - test: { - type: SchemaType.Text, - capabilities: { - fulltext: true, - filter: true, - facet: true, - sort: true, - snippet: true, - boost: true, - }, - }, - }, - apiTokens: [], - apiKey: 'some-key', - adaptive_relevance_suggestions_active: true, - incompleteFields: [], - }; - - const DEFAULT_VALUES = { - dataLoading: true, - engine: {}, - engineName: '', - hasNoDocuments: true, - hasEmptySchema: true, - isMetaEngine: false, - isElasticsearchEngine: false, - isSampleEngine: false, - hasSchemaErrors: false, - hasSchemaConflicts: false, - hasUnconfirmedSchemaFields: false, - engineNotFound: false, - searchKey: '', - intervalId: null, - hasIncompleteFields: false, - }; - - const DEFAULT_VALUES_WITH_ENGINE = { - ...DEFAULT_VALUES, - engine: mockEngineData, - hasNoDocuments: false, - hasEmptySchema: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(EngineLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('setEngineData', () => { - describe('engine & dataLoading', () => { - it('should set engine to the provided value and dataLoading to false', () => { - mount(); - EngineLogic.actions.setEngineData(mockEngineData); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine: mockEngineData, - dataLoading: false, - }); - }); - }); - }); - - describe('setEngineName', () => { - describe('engineName', () => { - it('should be set to the provided value', () => { - mount(); - EngineLogic.actions.setEngineName('some-new-engine'); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - engineName: 'some-new-engine', - }); - }); - }); - }); - - describe('setEngineNotFound', () => { - describe('engineNotFound', () => { - it('should be set to the provided value', () => { - mount(); - EngineLogic.actions.setEngineNotFound(true); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - engineNotFound: true, - }); - }); - }); - }); - - describe('clearEngine', () => { - describe('engine', () => { - it('should be reset to an empty obj', () => { - mount({ engine: mockEngineData }); - EngineLogic.actions.clearEngine(); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - engine: {}, - }); - }); - }); - - describe('engineName', () => { - it('should be reset to an empty string', () => { - mount({ engineName: 'hello-world' }); - EngineLogic.actions.clearEngine(); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - engineName: '', - }); - }); - }); - - describe('dataLoading', () => { - it('should be set to true', () => { - mount({ dataLoading: false }); - EngineLogic.actions.clearEngine(); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - }); - - describe('engineNotFound', () => { - it('should be set to false', () => { - mount({ engineNotFound: true }); - EngineLogic.actions.clearEngine(); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - engineNotFound: false, - }); - }); - }); - }); - - describe('onPollStart', () => { - it('should set intervalId', () => { - mount({ intervalId: null }); - EngineLogic.actions.onPollStart(123); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - intervalId: 123, - }); - }); - - describe('onPollStop', () => { - // Note: This does have to be a separate action following stopPolling(), rather - // than using stopPolling: () => null as a reducer. If you do that, then the ID - // gets cleared before the actual poll interval does & the poll interval never clears :doh: - - it('should reset intervalId', () => { - mount({ intervalId: 123 }); - EngineLogic.actions.onPollStop(); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - intervalId: null, - }); - }); - }); - }); - }); - - describe('listeners', () => { - describe('initializeEngine', () => { - it('fetches and sets engine data', async () => { - mount({ engineName: 'some-engine' }); - jest.spyOn(EngineLogic.actions, 'setEngineData'); - http.get.mockReturnValueOnce(Promise.resolve(mockEngineData)); - - EngineLogic.actions.initializeEngine(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine'); - expect(EngineLogic.actions.setEngineData).toHaveBeenCalledWith(mockEngineData); - }); - - it('handles 4xx errors', async () => { - mount(); - jest.spyOn(EngineLogic.actions, 'setEngineNotFound'); - http.get.mockReturnValue(Promise.reject({ response: { status: 404 } })); - - EngineLogic.actions.initializeEngine(); - await nextTick(); - - expect(EngineLogic.actions.setEngineNotFound).toHaveBeenCalledWith(true); - }); - - it('handles 5xx errors', async () => { - mount(); - http.get.mockReturnValue(Promise.reject('An error occured')); - - EngineLogic.actions.initializeEngine(); - await nextTick(); - - expect(flashErrorToast).toHaveBeenCalledWith('Could not fetch engine data', { - text: expect.stringContaining('Please check your connection'), - toastLifeTimeMs: 3750, - }); - }); - }); - - describe('pollEmptyEngine', () => { - beforeEach(() => jest.useFakeTimers({ legacyFakeTimers: true })); - afterEach(() => jest.clearAllTimers()); - afterAll(() => jest.useRealTimers()); - - it('starts a poll', () => { - mount(); - jest.spyOn(global, 'setInterval'); - jest.spyOn(EngineLogic.actions, 'onPollStart'); - - EngineLogic.actions.pollEmptyEngine(); - - expect(global.setInterval).toHaveBeenCalled(); - expect(EngineLogic.actions.onPollStart).toHaveBeenCalled(); - }); - - it('polls for engine data if the current engine has no documents', () => { - mount({ engine: { ...mockEngineData, document_count: 0 } }); - jest.spyOn(EngineLogic.actions, 'initializeEngine'); - - EngineLogic.actions.pollEmptyEngine(); - - jest.advanceTimersByTime(5000); - expect(EngineLogic.actions.initializeEngine).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(5000); - expect(EngineLogic.actions.initializeEngine).toHaveBeenCalledTimes(2); - }); - - it('cancels the poll if the current engine has documents', () => { - mount({ engine: { ...mockEngineData, document_count: 1 } }); - jest.spyOn(EngineLogic.actions, 'stopPolling'); - jest.spyOn(EngineLogic.actions, 'initializeEngine'); - - EngineLogic.actions.pollEmptyEngine(); - - jest.advanceTimersByTime(5000); - expect(EngineLogic.actions.stopPolling).toHaveBeenCalled(); - expect(EngineLogic.actions.initializeEngine).not.toHaveBeenCalled(); - }); - - it('does not create new polls if one already exists', () => { - jest.spyOn(global, 'setInterval'); - mount({ intervalId: 123 }); - - EngineLogic.actions.pollEmptyEngine(); - - expect(global.setInterval).not.toHaveBeenCalled(); - }); - }); - - describe('stopPolling', () => { - it('clears the poll interval and unsets the intervalId', () => { - jest.spyOn(global, 'clearInterval'); - mount({ intervalId: 123 }); - - EngineLogic.actions.stopPolling(); - - expect(global.clearInterval).toHaveBeenCalledWith(123); - expect(EngineLogic.values.intervalId).toEqual(null); - }); - - it('does not clearInterval if a poll has not been started', () => { - jest.spyOn(global, 'clearInterval'); - mount({ intervalId: null }); - - EngineLogic.actions.stopPolling(); - - expect(global.clearInterval).not.toHaveBeenCalled(); - }); - }); - }); - - describe('selectors', () => { - describe('hasNoDocuments', () => { - it('returns true if the engine contains no documents', () => { - const engine = { ...mockEngineData, document_count: 0 }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - hasNoDocuments: true, - }); - }); - - it('returns true if the engine is not yet initialized', () => { - mount({ engine: {} }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - hasNoDocuments: true, - }); - }); - }); - - describe('hasEmptySchema', () => { - it('returns true if the engine schema contains no fields', () => { - const engine = { ...mockEngineData, schema: {} }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - hasEmptySchema: true, - }); - }); - - it('returns true if the engine is not yet initialized', () => { - mount({ engine: {} }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES, - hasEmptySchema: true, - }); - }); - }); - - describe('isSampleEngine', () => { - it('should be set based on engine.sample', () => { - const engine = { ...mockEngineData, sample: true }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - isSampleEngine: true, - }); - }); - }); - - describe('isMetaEngine', () => { - it('should be set based on engine.type', () => { - const engine = { ...mockEngineData, type: EngineTypes.meta }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - isMetaEngine: true, - }); - }); - }); - - describe('isElasticsearchEngine', () => { - it('should be set based on engine.type', () => { - const engine = { ...mockEngineData, type: EngineTypes.elasticsearch }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - isElasticsearchEngine: true, - }); - }); - }); - - describe('hasSchemaErrors', () => { - it('should be set based on engine.activeReindexJob.numDocumentsWithErrors', () => { - const engine = { - ...mockEngineData, - activeReindexJob: { - numDocumentsWithErrors: 10, - }, - }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - hasSchemaErrors: true, - }); - }); - }); - - describe('hasSchemaConflicts', () => { - it('should be set based on engine.schemaConflicts', () => { - const engine = { - ...mockEngineData, - schemaConflicts: { - someSchemaField: { - fieldTypes: { - number: ['some-engine'], - date: ['another-engine'], - }, - }, - }, - }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - hasSchemaConflicts: true, - }); - }); - }); - - describe('hasUnconfirmedSchemaFields', () => { - it('should be set based on engine.unconfirmedFields', () => { - const engine = { - ...mockEngineData, - unconfirmedFields: ['new_field_1', 'new_field_2'], - }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - hasUnconfirmedSchemaFields: true, - }); - }); - }); - - describe('searchKey', () => { - it('should select the first available search key for this engine', () => { - const engine = { - ...mockEngineData, - apiTokens: [ - { - key: 'private-123xyz', - name: 'privateKey', - type: ApiTokenTypes.Private, - }, - { - key: 'search-123xyz', - name: 'searchKey', - type: ApiTokenTypes.Search, - }, - { - key: 'search-8910abc', - name: 'searchKey2', - type: ApiTokenTypes.Search, - }, - ], - }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - searchKey: 'search-123xyz', - }); - }); - - it('should return an empty string if none are available', () => { - const engine = { - ...mockEngineData, - apiTokens: [ - { - key: 'private-123xyz', - name: 'privateKey', - type: ApiTokenTypes.Private, - }, - ], - }; - mount({ engine }); - - expect(EngineLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_ENGINE, - engine, - searchKey: '', - }); - }); - }); - }); - - describe('events', () => { - it('calls stopPolling before unmount', () => { - mount(); - // Has to be a const to check state after unmount - const stopPollingSpy = jest.spyOn(EngineLogic.actions, 'stopPolling'); - - unmount(); - expect(stopPollingSpy).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts deleted file mode 100644 index 8e5e119747ae8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.ts +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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 { flashErrorToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { ApiTokenTypes } from '../credentials/constants'; -import { ApiToken } from '../credentials/types'; - -import { POLLING_DURATION, POLLING_ERROR_TITLE, POLLING_ERROR_TEXT } from './constants'; -import { EngineDetails, EngineTypes } from './types'; - -interface EngineValues { - dataLoading: boolean; - engine: Partial; - engineName: string; - hasNoDocuments: boolean; - hasEmptySchema: boolean; - isMetaEngine: boolean; - isElasticsearchEngine: boolean; - isSampleEngine: boolean; - hasSchemaErrors: boolean; - hasSchemaConflicts: boolean; - hasUnconfirmedSchemaFields: boolean; - engineNotFound: boolean; - searchKey: string; - intervalId: number | null; - hasIncompleteFields: boolean; -} - -interface EngineActions { - setEngineData(engine: EngineDetails): { engine: EngineDetails }; - setEngineName(engineName: string): { engineName: string }; - setEngineNotFound(notFound: boolean): { notFound: boolean }; - clearEngine(): void; - initializeEngine(): void; - pollEmptyEngine(): void; - onPollStart(intervalId: number): { intervalId: number }; - stopPolling(): void; - onPollStop(): void; -} - -export const EngineLogic = kea>({ - path: ['enterprise_search', 'app_search', 'engine_logic'], - actions: { - setEngineData: (engine) => ({ engine }), - setEngineName: (engineName) => ({ engineName }), - setEngineNotFound: (notFound) => ({ notFound }), - clearEngine: true, - initializeEngine: true, - pollEmptyEngine: true, - onPollStart: (intervalId) => ({ intervalId }), - stopPolling: true, - onPollStop: true, - }, - reducers: { - dataLoading: [ - true, - { - setEngineData: () => false, - clearEngine: () => true, - }, - ], - engine: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - setEngineData: (_, { engine }) => engine, - clearEngine: () => ({}), - }, - ], - engineName: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setEngineName: (_, { engineName }) => engineName, - clearEngine: () => '', - }, - ], - engineNotFound: [ - false, - { - // @ts-expect-error upgrade typescript v5.1.6 - setEngineNotFound: (_, { notFound }) => notFound, - clearEngine: () => false, - }, - ], - intervalId: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - onPollStart: (_, { intervalId }) => intervalId, - onPollStop: () => null, - }, - ], - }, - selectors: ({ selectors }) => ({ - hasNoDocuments: [() => [selectors.engine], (engine) => !engine.document_count], - hasEmptySchema: [ - () => [selectors.engine], - (engine) => Object.keys(engine.schema || {}).length === 0, - ], - isMetaEngine: [() => [selectors.engine], (engine) => engine?.type === EngineTypes.meta], - isElasticsearchEngine: [ - () => [selectors.engine], - (engine) => engine?.type === EngineTypes.elasticsearch, - ], - isSampleEngine: [() => [selectors.engine], (engine) => !!engine?.sample], - // Indexed engines - hasSchemaErrors: [ - () => [selectors.engine], - ({ activeReindexJob }) => activeReindexJob?.numDocumentsWithErrors > 0, - ], - // Meta engines - hasSchemaConflicts: [ - () => [selectors.engine], - (engine) => !!(engine?.schemaConflicts && Object.keys(engine.schemaConflicts).length > 0), - ], - hasUnconfirmedSchemaFields: [ - () => [selectors.engine], - (engine) => engine?.unconfirmedFields?.length > 0, - ], - searchKey: [ - () => [selectors.engine], - (engine: Partial) => { - const isSearchKey = (token: ApiToken) => token.type === ApiTokenTypes.Search; - const searchKey = (engine.apiTokens || []).find(isSearchKey); - return searchKey?.key || ''; - }, - ], - hasIncompleteFields: [ - () => [selectors.engine], - ({ incompleteFields }) => incompleteFields?.length > 0, - ], - }), - listeners: ({ actions, values }) => ({ - initializeEngine: async (_, breakpoint) => { - breakpoint(); // Prevents errors if logic unmounts while fetching - - const { engineName } = values; - const { http } = HttpLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}` - ); - actions.setEngineData(response); - } catch (error) { - if (error?.response?.status >= 400 && error?.response?.status < 500) { - actions.setEngineNotFound(true); - } else { - flashErrorToast(POLLING_ERROR_TITLE, { - text: POLLING_ERROR_TEXT, - toastLifeTimeMs: POLLING_DURATION * 0.75, - }); - } - } - }, - pollEmptyEngine: () => { - if (values.intervalId) return; // Ensure we only have one poll at a time - - const id = window.setInterval(() => { - if (values.hasNoDocuments) { - actions.initializeEngine(); // Re-fetch engine data when engine is empty - } else { - actions.stopPolling(); - } - }, POLLING_DURATION); - - actions.onPollStart(id); - }, - stopPolling: () => { - if (values.intervalId !== null) { - clearInterval(values.intervalId); - actions.onPollStop(); - } - }, - }), - events: ({ actions }) => ({ - beforeUnmount: () => { - actions.stopPolling(); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss deleted file mode 100644 index 61eb6fbf6ae09..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.scss +++ /dev/null @@ -1,15 +0,0 @@ -.appSearchNavEngineLabel { - margin-left: $euiSizeS; - padding-top: $euiSizeXS; - padding-bottom: $euiSizeS; - - .euiBadge { - margin-top: $euiSizeXS; - } -} - -.appSearchNavIcon { - // EuiSideNav renders icons to the left of the nav link by default, but we use icons - // as warning or error indicators & prefer to render them on the right side of the nav - order: 1; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx deleted file mode 100644 index f5baf9dcc9b3d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx +++ /dev/null @@ -1,332 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; -import { mockUseRouteMatch } from '../../../__mocks__/react_router'; -import { mockEngineValues } from '../../__mocks__'; - -jest.mock('../../../shared/layout', () => ({ - generateNavLink: jest.fn(({ to }) => ({ href: to })), -})); - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBadge, EuiIcon } from '@elastic/eui'; - -import { useEngineNav } from './engine_nav'; - -describe('useEngineNav', () => { - const values = { ...mockEngineValues, myRole: {}, dataLoading: false }; - - beforeEach(() => { - setMockValues(values); - mockUseRouteMatch.mockReturnValue(true); - }); - - describe('returns empty', () => { - it('does not return engine nav items if not on an engine route', () => { - mockUseRouteMatch.mockReturnValueOnce(false); - expect(useEngineNav()).toBeUndefined(); - }); - - it('does not return engine nav items if data is still loading', () => { - setMockValues({ ...values, dataLoading: true }); - expect(useEngineNav()).toBeUndefined(); - }); - - it('does not return engine nav items if engine data is missing', () => { - setMockValues({ ...values, engineName: '' }); - expect(useEngineNav()).toBeUndefined(); - }); - }); - - describe('returns an array of EUI side nav items', () => { - const BASE_NAV = [ - { - id: 'engineName', - name: 'some-engine', - renderItem: expect.any(Function), - 'data-test-subj': 'EngineLabel', - }, - { - id: 'overview', - name: 'Overview', - href: '/engines/some-engine', - 'data-test-subj': 'EngineOverviewLink', - }, - ]; - - it('always returns an engine label and overview link', () => { - expect(useEngineNav()).toEqual(BASE_NAV); - }); - - describe('engine label', () => { - const renderEngineLabel = (engineNav: any) => { - return shallow(engineNav[0].renderItem() as any); - }; - - it('renders the capitalized engine name', () => { - const wrapper = renderEngineLabel(useEngineNav()); - const name = wrapper.find('.eui-textTruncate'); - - expect(name.text()).toEqual('SOME-ENGINE'); - expect(wrapper.find(EuiBadge)).toHaveLength(0); - }); - - it('renders a sample engine badge for the sample engine', () => { - setMockValues({ ...values, isSampleEngine: true }); - const wrapper = renderEngineLabel(useEngineNav()); - - expect(wrapper.find(EuiBadge).prop('children')).toEqual('SAMPLE ENGINE'); - }); - - it('renders a meta engine badge for meta engines', () => { - setMockValues({ ...values, isMetaEngine: true }); - const wrapper = renderEngineLabel(useEngineNav()); - - expect(wrapper.find(EuiBadge).prop('children')).toEqual('META ENGINE'); - }); - - it('renders an elasticsearch index badge for elasticsearch indexed engines', () => { - setMockValues({ ...values, isElasticsearchEngine: true }); - const wrapper = renderEngineLabel(useEngineNav()); - - expect(wrapper.find(EuiBadge).prop('children')).toEqual('ELASTICSEARCH INDEX'); - }); - }); - - it('returns an analytics nav item', () => { - setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'analytics', - name: 'Analytics', - href: '/engines/some-engine/analytics', - 'data-test-subj': 'EngineAnalyticsLink', - }, - ]); - }); - - it('returns a documents nav item', () => { - setMockValues({ ...values, myRole: { canViewEngineDocuments: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'documents', - name: 'Documents', - href: '/engines/some-engine/documents', - 'data-test-subj': 'EngineDocumentsLink', - }, - ]); - }); - - it('returns a schema nav item', () => { - setMockValues({ ...values, myRole: { canViewEngineSchema: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'schema', - name: 'Schema', - href: '/engines/some-engine/schema', - 'data-test-subj': 'EngineSchemaLink', - icon: expect.anything(), - }, - ]); - }); - - describe('schema nav icons', () => { - const myRole = { canViewEngineSchema: true }; - - const renderIcons = (engineNav: any) => { - return shallow(
    {engineNav[2].icon}
    ); - }; - - it('renders schema errors alert icon', () => { - setMockValues({ ...values, myRole, hasSchemaErrors: true }); - const wrapper = renderIcons(useEngineNav()); - - expect(wrapper.find('[data-test-subj="EngineNavSchemaErrors"]')).toHaveLength(1); - }); - - it('renders unconfirmed schema fields info icon', () => { - setMockValues({ ...values, myRole, hasUnconfirmedSchemaFields: true }); - const wrapper = renderIcons(useEngineNav()); - - expect(wrapper.find('[data-test-subj="EngineNavSchemaUnconfirmedFields"]')).toHaveLength(1); - }); - - it('renders schema conflicts alert icon', () => { - setMockValues({ ...values, myRole, hasSchemaConflicts: true }); - const wrapper = renderIcons(useEngineNav()); - - expect(wrapper.find('[data-test-subj="EngineNavSchemaConflicts"]')).toHaveLength(1); - }); - }); - - describe('crawler', () => { - const myRole = { canViewEngineCrawler: true }; - - it('returns a crawler nav item', () => { - setMockValues({ ...values, myRole }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'crawler', - name: 'Web Crawler', - href: '/engines/some-engine/crawler', - 'data-test-subj': 'EngineCrawlerLink', - }, - ]); - }); - - it('does not return a crawler nav item for meta engines', () => { - setMockValues({ ...values, myRole, isMetaEngine: true }); - expect(useEngineNav()).toEqual(BASE_NAV); - }); - - it('does not return a crawler nav item for elasticsearch engines', () => { - setMockValues({ ...values, myRole, isElasticsearchEngine: true }); - expect(useEngineNav()).toEqual(BASE_NAV); - }); - }); - - describe('meta engine source engines', () => { - const myRole = { canViewMetaEngineSourceEngines: true }; - - it('returns a source engines nav item', () => { - setMockValues({ ...values, myRole, isMetaEngine: true }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'sourceEngines', - name: 'Engines', - href: '/engines/some-engine/engines', - 'data-test-subj': 'MetaEngineEnginesLink', - }, - ]); - }); - - it('does not return a source engines nav item for non-meta engines', () => { - setMockValues({ ...values, myRole, isMetaEngine: false }); - expect(useEngineNav()).toEqual(BASE_NAV); - }); - }); - - it('returns a relevance tuning nav item', () => { - setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'relevanceTuning', - name: 'Relevance Tuning', - href: '/engines/some-engine/relevance_tuning', - 'data-test-subj': 'EngineRelevanceTuningLink', - icon: expect.anything(), - }, - ]); - }); - - describe('relevance tuning nav icons', () => { - const myRole = { canManageEngineRelevanceTuning: true }; - - const renderIcons = (engineNav: any) => { - return shallow(
    {engineNav[2].icon}
    ); - }; - - it('renders unconfirmed schema fields info icon', () => { - setMockValues({ ...values, myRole, engine: { unsearchedUnconfirmedFields: true } }); - const wrapper = renderIcons(useEngineNav()); - expect( - wrapper.find('[data-test-subj="EngineNavRelevanceTuningUnsearchedFields"]') - ).toHaveLength(1); - }); - - it('renders schema conflicts alert icon', () => { - setMockValues({ ...values, myRole, engine: { invalidBoosts: true } }); - const wrapper = renderIcons(useEngineNav()); - expect( - wrapper.find('[data-test-subj="EngineNavRelevanceTuningInvalidBoosts"]') - ).toHaveLength(1); - }); - - it('can render multiple icons', () => { - const engine = { invalidBoosts: true, unsearchedUnconfirmedFields: true }; - setMockValues({ ...values, myRole, engine }); - const wrapper = renderIcons(useEngineNav()); - expect(wrapper.find(EuiIcon)).toHaveLength(2); - }); - }); - - it('returns a synonyms nav item', () => { - setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'synonyms', - name: 'Synonyms', - href: '/engines/some-engine/synonyms', - 'data-test-subj': 'EngineSynonymsLink', - }, - ]); - }); - - it('returns a curations nav item', () => { - setMockValues({ ...values, myRole: { canManageEngineCurations: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'curations', - name: 'Curations', - href: '/engines/some-engine/curations', - 'data-test-subj': 'EngineCurationsLink', - }, - ]); - }); - - it('returns a results settings nav item', () => { - setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'resultSettings', - name: 'Result Settings', - href: '/engines/some-engine/result_settings', - 'data-test-subj': 'EngineResultSettingsLink', - }, - ]); - }); - - it('returns a Search UI nav item', () => { - setMockValues({ ...values, myRole: { canManageEngineSearchUi: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'searchUI', - name: 'Search UI', - href: '/engines/some-engine/search_ui', - 'data-test-subj': 'EngineSearchUILink', - }, - ]); - }); - - it('returns an API logs nav item', () => { - setMockValues({ ...values, myRole: { canViewEngineApiLogs: true } }); - expect(useEngineNav()).toEqual([ - ...BASE_NAV, - { - id: 'apiLogs', - name: 'API Logs', - href: '/engines/some-engine/api_logs', - 'data-test-subj': 'EngineAPILogsLink', - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx deleted file mode 100644 index f57118999d8ee..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ /dev/null @@ -1,321 +0,0 @@ -/* - * 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 { useRouteMatch } from 'react-router-dom'; - -import { useValues } from 'kea'; - -import { EuiSideNavItemType, EuiText, EuiBadge, EuiIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { generateNavLink } from '../../../shared/layout'; -import { AppLogic } from '../../app_logic'; -import { - ENGINE_PATH, - ENGINE_ANALYTICS_PATH, - ENGINE_DOCUMENTS_PATH, - ENGINE_SCHEMA_PATH, - ENGINE_CRAWLER_PATH, - META_ENGINE_SOURCE_ENGINES_PATH, - ENGINE_RELEVANCE_TUNING_PATH, - ENGINE_SYNONYMS_PATH, - ENGINE_CURATIONS_PATH, - ENGINE_RESULT_SETTINGS_PATH, - ENGINE_SEARCH_UI_PATH, - ENGINE_API_LOGS_PATH, -} from '../../routes'; -import { ANALYTICS_TITLE } from '../analytics'; -import { API_LOGS_TITLE } from '../api_logs'; -import { CRAWLER_TITLE } from '../crawler'; -import { CURATIONS_TITLE } from '../curations'; -import { DOCUMENTS_TITLE } from '../documents'; -import { OVERVIEW_TITLE } from '../engine_overview'; -import { ENGINES_TITLE } from '../engines'; -import { RELEVANCE_TUNING_TITLE } from '../relevance_tuning'; -import { RESULT_SETTINGS_TITLE } from '../result_settings'; -import { SCHEMA_TITLE } from '../schema'; -import { SEARCH_UI_TITLE } from '../search_ui'; -import { SYNONYMS_TITLE } from '../synonyms'; - -import { EngineLogic, generateEnginePath } from '.'; - -import './engine_nav.scss'; - -export const useEngineNav = () => { - const isEngineRoute = !!useRouteMatch(ENGINE_PATH); - const { - myRole: { - canViewEngineAnalytics, - canViewEngineDocuments, - canViewEngineSchema, - canViewEngineCrawler, - canViewMetaEngineSourceEngines, - canManageEngineSynonyms, - canManageEngineCurations, - canManageEngineRelevanceTuning, - canManageEngineResultSettings, - canManageEngineSearchUi, - canViewEngineApiLogs, - }, - } = useValues(AppLogic); - const { - engineName, - dataLoading, - isSampleEngine, - isMetaEngine, - isElasticsearchEngine, - hasSchemaErrors, - hasSchemaConflicts, - hasUnconfirmedSchemaFields, - engine, - hasIncompleteFields, - } = useValues(EngineLogic); - - if (!isEngineRoute) return undefined; - if (dataLoading) return undefined; - if (!engineName) return undefined; - - const navItems: Array> = [ - { - id: 'engineName', - name: engineName, - renderItem: () => ( - -
    {engineName.toUpperCase()}
    - {isSampleEngine && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.sampleEngineBadge', { - defaultMessage: 'SAMPLE ENGINE', - })} - - )} - {isMetaEngine && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.metaEngineBadge', { - defaultMessage: 'META ENGINE', - })} - - )} - {isElasticsearchEngine && ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.elasticsearchEngineBadge', { - defaultMessage: 'ELASTICSEARCH INDEX', - })} - - )} -
    - ), - 'data-test-subj': 'EngineLabel', - }, - { - id: 'overview', - name: OVERVIEW_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_PATH) }), - 'data-test-subj': 'EngineOverviewLink', - }, - ]; - - if (canViewEngineAnalytics) { - navItems.push({ - id: 'analytics', - name: ANALYTICS_TITLE, - ...generateNavLink({ - to: generateEnginePath(ENGINE_ANALYTICS_PATH), - shouldShowActiveForSubroutes: true, - }), - 'data-test-subj': 'EngineAnalyticsLink', - }); - } - - if (canViewEngineDocuments) { - navItems.push({ - id: 'documents', - name: DOCUMENTS_TITLE, - ...generateNavLink({ - to: generateEnginePath(ENGINE_DOCUMENTS_PATH), - shouldShowActiveForSubroutes: true, - }), - 'data-test-subj': 'EngineDocumentsLink', - }); - } - - if (canViewEngineSchema) { - navItems.push({ - id: 'schema', - name: SCHEMA_TITLE, - ...generateNavLink({ - to: generateEnginePath(ENGINE_SCHEMA_PATH), - shouldShowActiveForSubroutes: true, - }), - 'data-test-subj': 'EngineSchemaLink', - icon: ( - <> - {hasSchemaErrors && ( - - )} - {hasUnconfirmedSchemaFields && ( - - )} - {hasSchemaConflicts && ( - - )} - {hasIncompleteFields && ( - - )} - - ), - }); - } - - const showCrawlerNavItem = canViewEngineCrawler && !isMetaEngine && !isElasticsearchEngine; - if (showCrawlerNavItem) { - navItems.push({ - id: 'crawler', - name: CRAWLER_TITLE, - ...generateNavLink({ - to: generateEnginePath(ENGINE_CRAWLER_PATH), - shouldShowActiveForSubroutes: true, - }), - 'data-test-subj': 'EngineCrawlerLink', - }); - } - - if (canViewMetaEngineSourceEngines && isMetaEngine) { - navItems.push({ - id: 'sourceEngines', - name: ENGINES_TITLE, - ...generateNavLink({ to: generateEnginePath(META_ENGINE_SOURCE_ENGINES_PATH) }), - 'data-test-subj': 'MetaEngineEnginesLink', - }); - } - - if (canManageEngineRelevanceTuning) { - const { invalidBoosts, unsearchedUnconfirmedFields } = engine; - - navItems.push({ - id: 'relevanceTuning', - name: RELEVANCE_TUNING_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_RELEVANCE_TUNING_PATH) }), - 'data-test-subj': 'EngineRelevanceTuningLink', - icon: ( - <> - {invalidBoosts && ( - - )} - {unsearchedUnconfirmedFields && ( - - )} - - ), - }); - } - - if (canManageEngineSynonyms) { - navItems.push({ - id: 'synonyms', - name: SYNONYMS_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_SYNONYMS_PATH) }), - 'data-test-subj': 'EngineSynonymsLink', - }); - } - - if (canManageEngineCurations) { - navItems.push({ - id: 'curations', - name: CURATIONS_TITLE, - ...generateNavLink({ - to: generateEnginePath(ENGINE_CURATIONS_PATH), - shouldShowActiveForSubroutes: true, - }), - 'data-test-subj': 'EngineCurationsLink', - }); - } - - if (canManageEngineResultSettings) { - navItems.push({ - id: 'resultSettings', - name: RESULT_SETTINGS_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_RESULT_SETTINGS_PATH) }), - 'data-test-subj': 'EngineResultSettingsLink', - }); - } - - if (canManageEngineSearchUi) { - navItems.push({ - id: 'searchUI', - name: SEARCH_UI_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_SEARCH_UI_PATH) }), - 'data-test-subj': 'EngineSearchUILink', - }); - } - - if (canViewEngineApiLogs) { - navItems.push({ - id: 'apiLogs', - name: API_LOGS_TITLE, - ...generateNavLink({ to: generateEnginePath(ENGINE_API_LOGS_PATH) }), - 'data-test-subj': 'EngineAPILogsLink', - }); - } - - return navItems; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx deleted file mode 100644 index 4a7f72a259ea8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 { - mockFlashMessageHelpers, - setMockValues, - setMockActions, -} from '../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../__mocks__/react_router'; -import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; -import { mockEngineValues } from '../../__mocks__'; - -import React from 'react'; -import { Redirect } from 'react-router-dom'; - -import { shallow } from 'enzyme'; - -import { Routes } from '@kbn/shared-ux-router'; - -import { AnalyticsRouter } from '../analytics'; -import { ApiLogs } from '../api_logs'; -import { CrawlerRouter } from '../crawler'; -import { CurationsRouter } from '../curations'; -import { Documents, DocumentDetail } from '../documents'; -import { EngineOverview } from '../engine_overview'; -import { RelevanceTuning } from '../relevance_tuning'; -import { ResultSettings } from '../result_settings'; -import { SchemaRouter } from '../schema'; -import { SearchUI } from '../search_ui'; -import { SourceEngines } from '../source_engines'; -import { Synonyms } from '../synonyms'; - -import { EngineRouter } from './engine_router'; - -describe('EngineRouter', () => { - const values = { - ...mockEngineValues, - dataLoading: false, - engineNotFound: false, - isMetaEngine: false, - myRole: {}, - }; - const actions = { - setEngineName: jest.fn(), - initializeEngine: jest.fn(), - pollEmptyEngine: jest.fn(), - stopPolling: jest.fn(), - clearEngine: jest.fn(), - }; - - beforeEach(() => { - setMockValues(values); - setMockActions(actions); - mockUseParams.mockReturnValue({ engineName: 'some-engine' }); - }); - - describe('useEffect', () => { - beforeEach(() => { - shallow(); - }); - - it('sets engineName based on the current route parameters', () => { - expect(actions.setEngineName).toHaveBeenCalledWith('some-engine'); - }); - - it('initializes/fetches engine API data and starts a poll for empty engines', () => { - expect(actions.initializeEngine).toHaveBeenCalled(); - expect(actions.pollEmptyEngine).toHaveBeenCalled(); - }); - - it('clears engine and stops polling on unmount / on engine change', () => { - unmountHandler(); - expect(actions.stopPolling).toHaveBeenCalled(); - expect(actions.clearEngine).toHaveBeenCalled(); - }); - }); - - it('redirects to engines list and flashes an error if the engine param was not found', () => { - const { setQueuedErrorMessage } = mockFlashMessageHelpers; - setMockValues({ ...values, engineNotFound: true, engineName: '404-engine' }); - const wrapper = shallow(); - - expect(wrapper.find(Redirect).prop('to')).toEqual('/engines'); - expect(setQueuedErrorMessage).toHaveBeenCalledWith( - "No engine with name '404-engine' could be found." - ); - }); - - it('renders a loading page template if async data is still loading', () => { - setMockValues({ ...values, dataLoading: true }); - const wrapper = shallow(); - expect(wrapper.prop('isLoading')).toEqual(true); - }); - - // This would happen if a user jumps around from one engine route to another. If the engine name - // on the path has changed, but we still have an engine stored in state, we do not want to load - // any route views as they would be rendering with the wrong data. - it('renders a loading page template if the engine stored in state is stale', () => { - setMockValues({ ...values, engineName: 'some-engine' }); - mockUseParams.mockReturnValue({ engineName: 'some-new-engine' }); - const wrapper = shallow(); - expect(wrapper.prop('isLoading')).toEqual(true); - }); - - it('renders a default engine overview', () => { - const wrapper = shallow(); - - expect(wrapper.find(Routes)).toHaveLength(1); - expect(wrapper.find(EngineOverview)).toHaveLength(1); - }); - - it('renders an analytics view', () => { - setMockValues({ ...values, myRole: { canViewEngineAnalytics: true } }); - const wrapper = shallow(); - - expect(wrapper.find(AnalyticsRouter)).toHaveLength(1); - }); - - it('renders a documents view', () => { - setMockValues({ ...values, myRole: { canViewEngineDocuments: true } }); - const wrapper = shallow(); - - expect(wrapper.find(Documents)).toHaveLength(1); - expect(wrapper.find(DocumentDetail)).toHaveLength(1); - }); - - it('renders a schema view', () => { - setMockValues({ ...values, myRole: { canViewEngineSchema: true } }); - const wrapper = shallow(); - - expect(wrapper.find(SchemaRouter)).toHaveLength(1); - }); - - it('renders a synonyms view', () => { - setMockValues({ ...values, myRole: { canManageEngineSynonyms: true } }); - const wrapper = shallow(); - - expect(wrapper.find(Synonyms)).toHaveLength(1); - }); - - it('renders a curations view', () => { - setMockValues({ ...values, myRole: { canManageEngineCurations: true } }); - const wrapper = shallow(); - - expect(wrapper.find(CurationsRouter)).toHaveLength(1); - }); - - it('renders a relevance tuning view', () => { - setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } }); - const wrapper = shallow(); - - expect(wrapper.find(RelevanceTuning)).toHaveLength(1); - }); - - it('renders a result settings view', () => { - setMockValues({ ...values, myRole: { canManageEngineResultSettings: true } }); - const wrapper = shallow(); - - expect(wrapper.find(ResultSettings)).toHaveLength(1); - }); - - it('renders an API logs view', () => { - setMockValues({ ...values, myRole: { canViewEngineApiLogs: true } }); - const wrapper = shallow(); - - expect(wrapper.find(ApiLogs)).toHaveLength(1); - }); - - it('renders a search ui view', () => { - setMockValues({ ...values, myRole: { canManageEngineSearchUi: true } }); - const wrapper = shallow(); - - expect(wrapper.find(SearchUI)).toHaveLength(1); - }); - - it('renders a source engines view', () => { - setMockValues({ - ...values, - myRole: { canViewMetaEngineSourceEngines: true }, - isMetaEngine: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(SourceEngines)).toHaveLength(1); - }); - - it('renders a crawler view', () => { - setMockValues({ ...values, myRole: { canViewEngineCrawler: true }, isMetaEngine: false }); - const wrapper = shallow(); - - expect(wrapper.find(CrawlerRouter)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx deleted file mode 100644 index 0befcaa80b772..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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, { useEffect } from 'react'; -import { Redirect, useParams } from 'react-router-dom'; - -import { useValues, useActions } from 'kea'; - -import { i18n } from '@kbn/i18n'; -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { setQueuedErrorMessage } from '../../../shared/flash_messages'; -import { AppLogic } from '../../app_logic'; - -import { - ENGINE_PATH, - ENGINES_PATH, - ENGINE_ANALYTICS_PATH, - ENGINE_DOCUMENTS_PATH, - ENGINE_DOCUMENT_DETAIL_PATH, - ENGINE_SCHEMA_PATH, - ENGINE_CRAWLER_PATH, - META_ENGINE_SOURCE_ENGINES_PATH, - ENGINE_RELEVANCE_TUNING_PATH, - ENGINE_SYNONYMS_PATH, - ENGINE_CURATIONS_PATH, - ENGINE_RESULT_SETTINGS_PATH, - ENGINE_SEARCH_UI_PATH, - ENGINE_API_LOGS_PATH, -} from '../../routes'; -import { AnalyticsRouter } from '../analytics'; -import { ApiLogs } from '../api_logs'; -import { CrawlerRouter } from '../crawler'; -import { CurationsRouter } from '../curations'; -import { DocumentDetail, Documents } from '../documents'; -import { EngineOverview } from '../engine_overview'; -import { AppSearchPageTemplate } from '../layout'; -import { NotFound } from '../not_found'; -import { RelevanceTuning } from '../relevance_tuning'; -import { ResultSettings } from '../result_settings'; -import { SchemaRouter } from '../schema'; -import { SearchUI } from '../search_ui'; -import { SourceEngines } from '../source_engines'; -import { Synonyms } from '../synonyms'; - -import { EngineLogic, getEngineBreadcrumbs } from '.'; - -export const EngineRouter: React.FC = () => { - const { - myRole: { - canViewEngineAnalytics, - canViewEngineDocuments, - canViewEngineSchema, - canViewEngineCrawler, - canViewMetaEngineSourceEngines, - canManageEngineRelevanceTuning, - canManageEngineSynonyms, - canManageEngineCurations, - canManageEngineResultSettings, - canManageEngineSearchUi, - canViewEngineApiLogs, - }, - } = useValues(AppLogic); - - const { engineName: engineNameFromUrl } = useParams() as { engineName: string }; - const { engineName, dataLoading, engineNotFound, isMetaEngine } = useValues(EngineLogic); - const { setEngineName, initializeEngine, pollEmptyEngine, stopPolling, clearEngine } = - useActions(EngineLogic); - - useEffect(() => { - setEngineName(engineNameFromUrl); - initializeEngine(); - pollEmptyEngine(); - - return () => { - stopPolling(); - clearEngine(); - }; - }, [engineNameFromUrl]); - - if (engineNotFound) { - setQueuedErrorMessage( - i18n.translate('xpack.enterpriseSearch.appSearch.engine.notFound', { - defaultMessage: "No engine with name ''{engineName}'' could be found.", - values: { engineName }, - }) - ); - return ; - } - - const isLoadingNewEngine = engineName !== engineNameFromUrl; - if (isLoadingNewEngine || dataLoading) return ; - - return ( - - - - - {canViewEngineAnalytics && ( - - - - )} - {canViewEngineDocuments && ( - - - - )} - {canViewEngineDocuments && ( - - - - )} - {canViewEngineSchema && ( - - - - )} - {canViewMetaEngineSourceEngines && isMetaEngine && ( - - - - )} - {canViewEngineCrawler && !isMetaEngine && ( - - - - )} - {canManageEngineRelevanceTuning && ( - - - - )} - {canManageEngineSynonyms && ( - - - - )} - {canManageEngineCurations && ( - - - - )} - {canManageEngineResultSettings && ( - - - - )} - {canManageEngineSearchUi && ( - - - - )} - {canViewEngineApiLogs && ( - - - - )} - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts deleted file mode 100644 index 86b4ff21e62d3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { EngineRouter } from './engine_router'; -export { EngineLogic } from './engine_logic'; -export { generateEnginePath, getEngineBreadcrumbs } from './utils'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts deleted file mode 100644 index e8c9dfaa63483..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/types.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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, - SchemaConflicts, - IIndexingStatus, - AdvancedSchema, -} from '../../../shared/schema/types'; -import { ApiToken } from '../credentials/types'; - -export enum EngineTypes { - default = 'default', - indexed = 'indexed', - meta = 'meta', - elasticsearch = 'elasticsearch', -} -export interface Engine { - name: string; - type: EngineTypes; - language: string | null; - result_fields: { - [key: string]: ResultField; - }; -} - -interface CurationSuggestionDetails { - count: number; - pending: number; - applied: number; - automated: number; - rejected: number; - disabled: number; - last_updated: string; -} - -interface SearchRelevanceSuggestionDetails { - count: number; - curation: CurationSuggestionDetails; -} - -export interface EngineDetails extends Engine { - created_at: string; - document_count: number; - field_count: number; - unsearchedUnconfirmedFields: boolean; - apiTokens: ApiToken[]; - apiKey: string; - elasticsearchIndexName?: string; - schema: Schema; - advancedSchema: AdvancedSchema; - schemaConflicts?: SchemaConflicts; - unconfirmedFields?: string[]; - activeReindexJob?: IIndexingStatus; - invalidBoosts: boolean; - sample?: boolean; - isMeta: boolean; - engine_count?: number; - includedEngines?: EngineDetails[]; - adaptive_relevance_suggestions?: SearchRelevanceSuggestionDetails; - adaptive_relevance_suggestions_active: boolean; - analytics_enabled?: boolean; -} - -interface ResultField { - raw: object; - snippet?: { - size: number; - fallback: boolean; - }; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts deleted file mode 100644 index be6b9a53bd0d5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { mockEngineValues } from '../../__mocks__'; - -import { generateEnginePath, getEngineBreadcrumbs } from './utils'; - -describe('generateEnginePath', () => { - beforeEach(() => { - mockEngineValues.engineName = 'hello-world'; - }); - - it('generates paths with engineName filled from state', () => { - expect(generateEnginePath('/engines/:engineName/example')).toEqual( - '/engines/hello-world/example' - ); - }); - - it('allows overriding engineName and filling other params', () => { - expect( - generateEnginePath('/engines/:engineName/foo/:bar', { - engineName: 'override', - bar: 'baz', - }) - ).toEqual('/engines/override/foo/baz'); - }); -}); - -describe('getEngineBreadcrumbs', () => { - beforeEach(() => { - mockEngineValues.engineName = 'foo'; - }); - - it('generates breadcrumbs with engineName filled from state', () => { - expect(getEngineBreadcrumbs(['bar', 'baz'])).toEqual(['Engines', 'foo', 'bar', 'baz']); - expect(getEngineBreadcrumbs(['bar'])).toEqual(['Engines', 'foo', 'bar']); - expect(getEngineBreadcrumbs()).toEqual(['Engines', 'foo']); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts deleted file mode 100644 index 28121607cf897..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs'; -import { generateEncodedPath } from '../../utils/encode_path_params'; - -import { ENGINES_TITLE } from '../engines'; - -import { EngineLogic } from '.'; - -/** - * Generate a path with engineName automatically filled from EngineLogic state - */ -export const generateEnginePath = (path: string, pathParams: object = {}) => { - const { engineName } = EngineLogic.values; - return generateEncodedPath(path, { engineName, ...pathParams }); -}; - -/** - * Generate a breadcrumb trail with engineName automatically filled from EngineLogic state - */ -export const getEngineBreadcrumbs = (breadcrumbs: BreadcrumbTrail = []) => { - const { engineName } = EngineLogic.values; - return [ENGINES_TITLE, engineName, ...breadcrumbs]; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_app_search_engine.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_app_search_engine.test.tsx deleted file mode 100644 index 8d2af69f28a03..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_app_search_engine.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { ConfigureAppSearchEngine } from './configure_app_search_engine'; - -describe('ConfigureAppSearchEngine', () => { - const DEFAULT_VALUES = { - name: '', - rawName: '', - language: 'Universal', - isSubmitDisabled: false, - }; - - const MOCK_ACTIONS = { - setRawName: jest.fn(), - setLanguage: jest.fn(), - submitEngine: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCreationForm"]')).toHaveLength(1); - }); - - it('EngineCreationForm calls submitEngine on form submit', () => { - const wrapper = shallow(); - const simulatedEvent = { - preventDefault: jest.fn(), - }; - wrapper.find('[data-test-subj="EngineCreationForm"]').simulate('submit', simulatedEvent); - - expect(MOCK_ACTIONS.submitEngine).toHaveBeenCalledTimes(1); - }); - - it('EngineCreationNameInput calls setRawName on change', () => { - const wrapper = shallow(); - const simulatedEvent = { - currentTarget: { value: 'new-raw-name' }, - }; - wrapper.find('[data-test-subj="EngineCreationNameInput"]').simulate('change', simulatedEvent); - - expect(MOCK_ACTIONS.setRawName).toHaveBeenCalledWith('new-raw-name'); - }); - - it('EngineCreationLanguageInput calls setLanguage on change', () => { - const wrapper = shallow(); - const simulatedEvent = { - currentTarget: { value: 'English' }, - }; - wrapper - .find('[data-test-subj="EngineCreationLanguageInput"]') - .simulate('change', simulatedEvent); - - expect(MOCK_ACTIONS.setLanguage).toHaveBeenCalledWith('English'); - }); - - describe('NewEngineSubmitButton', () => { - it('is disabled when name is empty', () => { - setMockValues({ ...DEFAULT_VALUES, name: '', rawName: '', isSubmitDisabled: true }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="NewEngineSubmitButton"]').prop('disabled')).toEqual( - true - ); - }); - - it('is enabled when name has a value', () => { - setMockValues({ ...DEFAULT_VALUES, name: 'test', rawName: 'test' }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="NewEngineSubmitButton"]').prop('disabled')).toEqual( - false - ); - }); - }); - - describe('EngineCreationNameFormRow', () => { - it('renders sanitized name helptext when the raw name is being sanitized', () => { - setMockValues({ - ...DEFAULT_VALUES, - name: 'un-sanitized-name', - rawName: 'un-----sanitized-------name', - }); - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="EngineCreationNameFormRow"]').dive(); - - expect(formRow.contains('Your engine will be named')).toBeTruthy(); - }); - - it('renders allowed character helptext when rawName and sanitizedName match', () => { - setMockValues({ - ...DEFAULT_VALUES, - name: 'pre-sanitized-name', - rawName: 'pre-sanitized-name', - }); - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="EngineCreationNameFormRow"]').dive(); - - expect( - formRow.contains('Engine names can only contain lowercase letters, numbers, and hyphens') - ).toBeTruthy(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_app_search_engine.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_app_search_engine.tsx deleted file mode 100644 index 2f6df58031fc3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_app_search_engine.tsx +++ /dev/null @@ -1,199 +0,0 @@ -/* - * 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, - EuiButtonEmpty, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiPanel, - EuiSelect, - EuiSpacer, - EuiStepsHorizontal, - EuiText, - EuiTextAlign, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { - ENGINE_CREATION_FORM_ENGINE_NAME_LABEL, - SANITIZED_NAME_NOTE, - ALLOWED_CHARS_NOTE, - ENGINE_CREATION_FORM_TITLE, - ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER, - ENGINE_CREATION_FORM_ENGINE_LANGUAGE_LABEL, - SUPPORTED_LANGUAGES, -} from './constants'; - -import { EngineCreationLogic, EngineCreationSteps } from './engine_creation_logic'; - -export const ConfigureAppSearchEngine: React.FC = () => { - const { isSubmitDisabled, language, name, rawName } = useValues(EngineCreationLogic); - const { setCreationStep, setLanguage, setRawName, submitEngine } = - useActions(EngineCreationLogic); - - return ( -
    - setCreationStep(EngineCreationSteps.SelectStep), - status: 'complete', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label', - { - defaultMessage: 'Search engine type', - } - ), - }, - { - onClick: () => {}, - status: 'current', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label', - { - defaultMessage: 'Configuration', - } - ), - }, - { - onClick: () => {}, - status: 'disabled', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.finish.label', - { - defaultMessage: 'Finish', - } - ), - }, - ]} - /> - - - - - { - e.preventDefault(); - submitEngine(); - }} - > - - -

    {ENGINE_CREATION_FORM_TITLE}

    -
    -
    - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.appSearch.description', - { - defaultMessage: - 'Provide a unique name and an optional language choice for your App Search engine.', - } - )} - - - - - - - 0 && rawName !== name ? ( - <> - {SANITIZED_NAME_NOTE} {name} - - ) : ( - ALLOWED_CHARS_NOTE - ) - } - fullWidth - > - setRawName(event.currentTarget.value)} - autoComplete="off" - fullWidth - data-test-subj="EngineCreationNameInput" - placeholder={ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER} - autoFocus - /> - - - - - setLanguage(event.currentTarget.value)} - /> - - - - - - - - - { - setCreationStep(EngineCreationSteps.SelectStep); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.backButton.label', - { - defaultMessage: 'Back', - } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.continue.label', - { - defaultMessage: 'Create search engine', - } - )} - - - -
    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_elasticsearch_engine.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_elasticsearch_engine.test.tsx deleted file mode 100644 index bacf707709823..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_elasticsearch_engine.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { ConfigureElasticsearchEngine } from './configure_elasticsearch_engine'; -import { EngineCreationSteps } from './engine_creation_logic'; - -describe('ConfigureElasticsearchEngine', () => { - const DEFAULT_VALUES = { - aliasName: '', - aliasRawName: '', - isSubmitDisabled: false, - name: '', - rawName: '', - }; - - const MOCK_ACTIONS = { - loadIndices: jest.fn(), - setAliasRawName: jest.fn(), - setCreationStep: jest.fn(), - setRawName: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCreationForm"]')).toHaveLength(1); - }); - - it('EngineCreationForm calls setCreationStep on form submit', () => { - const wrapper = shallow(); - const simulatedEvent = { - preventDefault: jest.fn(), - }; - wrapper.find('[data-test-subj="EngineCreationForm"]').simulate('submit', simulatedEvent); - - expect(MOCK_ACTIONS.setCreationStep).toHaveBeenCalledWith(EngineCreationSteps.ReviewStep); - }); - - it('NewEngineBackButton calls setCreationStep when clicked', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="NewEngineBackButton"]').simulate('click'); - - expect(MOCK_ACTIONS.setCreationStep).toHaveBeenCalledWith(EngineCreationSteps.SelectStep); - }); - - it('EngineCreationNameInput calls setRawName on change', () => { - const wrapper = shallow(); - const simulatedEvent = { - currentTarget: { value: 'new-raw-name' }, - }; - wrapper.find('[data-test-subj="EngineCreationNameInput"]').simulate('change', simulatedEvent); - - expect(MOCK_ACTIONS.setRawName).toHaveBeenCalledWith('new-raw-name'); - }); - - describe('NewEngineContinueButton', () => { - it('is disabled when isSubmitDisabled is true', () => { - setMockValues({ ...DEFAULT_VALUES, isSubmitDisabled: true }); - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewEngineContinueButton"]'); - - expect(submitButton.prop('disabled')).toEqual(true); - }); - - it('is enabled isSubmitDisabled is false', () => { - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewEngineContinueButton"]'); - - expect(submitButton.prop('disabled')).toEqual(false); - }); - }); - - describe('EngineCreationNameFormRow', () => { - it('renders sanitized name helptext when the raw name is being sanitized', () => { - setMockValues({ - ...DEFAULT_VALUES, - name: 'un-sanitized-name', - rawName: 'un-----sanitized-------name', - }); - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="EngineCreationNameFormRow"]').dive(); - - expect(formRow.contains('Your engine will be named')).toBeTruthy(); - }); - - it('renders allowed character helptext when rawName and sanitizedName match', () => { - setMockValues({ - ...DEFAULT_VALUES, - name: 'pre-sanitized-name', - rawName: 'pre-sanitized-name', - }); - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="EngineCreationNameFormRow"]').dive(); - - expect( - formRow.contains('Engine names can only contain lowercase letters, numbers, and hyphens') - ).toBeTruthy(); - }); - }); - - describe('AliasNameFormRow', () => { - it('renders sanitized aliasName helptext when the aliasRawName is being sanitized', () => { - setMockValues({ - ...DEFAULT_VALUES, - aliasName: 'un-sanitized-name', - aliasRawName: 'un-----sanitized-------name', - }); - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="AliasNameFormRow"]').dive(); - const expectedMessage = - "Alias names must be prefixed with 'search-' in order to be used with App Search engines. Your alias will be named"; - - expect(formRow.contains(expectedMessage)).toBeTruthy(); - }); - - it('renders prefix helptext when aliasRawName and aliasName match', () => { - setMockValues({ - ...DEFAULT_VALUES, - aliasName: 'pre-sanitized-name', - aliasRawName: 'pre-sanitized-name', - }); - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="AliasNameFormRow"]').dive(); - const expectedMessage = - "Alias names must be prefixed with 'search-' in order to be used with App Search engines"; - - expect(formRow.contains(expectedMessage)).toBeTruthy(); - }); - }); - - it('AliasNameInput calls setAliasRawName on change', () => { - const wrapper = shallow(); - const simulatedEvent = { - currentTarget: { value: 'search-new-raw-name' }, - }; - wrapper.find('[data-test-subj="AliasNameInput"]').simulate('change', simulatedEvent); - - expect(MOCK_ACTIONS.setAliasRawName).toHaveBeenCalledWith('search-new-raw-name'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_elasticsearch_engine.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_elasticsearch_engine.tsx deleted file mode 100644 index 364294ff188d2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/configure_elasticsearch_engine.tsx +++ /dev/null @@ -1,368 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiButton, - EuiButtonEmpty, - EuiCallOut, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiHighlight, - EuiPanel, - EuiSelectable, - EuiSpacer, - EuiStepsHorizontal, - EuiText, - EuiTextAlign, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { - ALLOWED_CHARS_NOTE, - ENGINE_CREATION_FORM_ENGINE_NAME_LABEL, - ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER, - ENGINE_CREATION_FORM_TITLE, - SANITIZED_NAME_NOTE, -} from './constants'; - -import { EngineCreationLogic, EngineCreationSteps } from './engine_creation_logic'; -import { IndexStatusDetails, SearchIndexSelectableOption } from './search_index_selectable'; - -import './engine_creation.scss'; - -const renderIndexOption = (option: SearchIndexSelectableOption, searchValue: string) => { - return ( - <> - {option.label ?? ''} - - - - ); -}; - -export const ConfigureElasticsearchEngine: React.FC = () => { - const { - aliasName, - aliasNameErrorMessage, - aliasRawName, - indicesFormatted, - isAliasAllowed, - isAliasRequired, - isLoading, - isLoadingIndices, - isSubmitDisabled, - name, - rawName, - showAliasNameErrorMessages, - } = useValues(EngineCreationLogic); - const { - loadIndices, - setIsAliasAllowed, - setAliasRawName, - setCreationStep, - setRawName, - setSelectedIndex, - } = useActions(EngineCreationLogic); - - const onChange = (options: SearchIndexSelectableOption[]) => { - const selectedOption = options.find((option) => option.checked === 'on'); - setSelectedIndex(selectedOption?.label ?? ''); - - // If this is an alias, remove the alias name. Do nothing if an option was deselected - if (selectedOption?.alias ?? false) setAliasRawName(''); - - // Set isAliasAllowed depending on if the selectedOption is an alias or not. - // Set it to true if an option was deselected. - setIsAliasAllowed(!selectedOption?.alias ?? true); - }; - - const aliasOptionalOrRequired = !isAliasAllowed - ? 'Disabled' - : isAliasRequired - ? 'Required' - : 'Optional'; - - useEffect(() => { - loadIndices(); - }, []); - - return ( -
    - setCreationStep(EngineCreationSteps.SelectStep), - status: 'complete', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label', - { - defaultMessage: 'Search engine type', - } - ), - }, - { - onClick: () => {}, - status: 'current', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label', - { - defaultMessage: 'Configuration', - } - ), - }, - { - onClick: () => {}, - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.review.label', - { - defaultMessage: 'Review', - } - ), - }, - ]} - /> - - - - - { - e.preventDefault(); - setCreationStep(EngineCreationSteps.ReviewStep); - }} - > - - -

    {ENGINE_CREATION_FORM_TITLE}

    -
    -
    - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.description', - { - defaultMessage: - 'Provide a unique name and select an index for your App Search engine.', - } - )} - - - - - - - 0 && rawName !== name ? ( - <> - {SANITIZED_NAME_NOTE} {name} - - ) : ( - ALLOWED_CHARS_NOTE - ) - } - fullWidth - > - setRawName(event.currentTarget.value)} - autoComplete="off" - fullWidth - data-test-subj="EngineCreationNameInput" - placeholder={ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER} - autoFocus - /> - - - - - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.callout.body', - { - defaultMessage: ` - App Search engines can only be created with indices or - aliases prefixed with "search-". If you select an index that - doesn’t start with "search-", an alias to that index will be - created and used. - `, - } - )} -

    -
    - - - - - - {(list, search) => ( - <> - {search} - {list} - - )} - - - - - - - - 0 && aliasRawName !== aliasName ? ( - <> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefixAndNamed.helpText', - { - defaultMessage: - "Alias names must be prefixed with 'search-' in order to be used with App Search engines. Your alias will be named", - } - )} -  {aliasName} - - ) : ( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.prefix.helpText', - { - defaultMessage: - "Alias names must be prefixed with 'search-' in order to be used with App Search engines", - } - ) - ) - } - fullWidth - isInvalid={showAliasNameErrorMessages} - error={aliasNameErrorMessage} - > - setAliasRawName(event.currentTarget.value)} - autoComplete="off" - fullWidth - data-test-subj="AliasNameInput" - prepend={aliasOptionalOrRequired} - disabled={!isAliasAllowed} - isInvalid={showAliasNameErrorMessages} - /> - - - - - - - - - setCreationStep(EngineCreationSteps.SelectStep)} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.backButton.label', - { - defaultMessage: 'Back', - } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.continue.label', - { - defaultMessage: 'Continue', - } - )} - - - -
    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/constants.ts deleted file mode 100644 index 4846760b90766..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/constants.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const DEFAULT_LANGUAGE = 'Universal'; - -export const ENGINE_CREATION_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.title', - { - defaultMessage: 'Create an engine', - } -); - -export const ENGINE_CREATION_FORM_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.title', - { - defaultMessage: 'Configure your search engine', - } -); - -export const ENGINE_CREATION_FORM_ENGINE_NAME_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.label', - { - defaultMessage: 'Engine name', - } -); - -export const ALLOWED_CHARS_NOTE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.allowedCharactersHelpText', - { - defaultMessage: 'Engine names can only contain lowercase letters, numbers, and hyphens', - } -); - -export const SANITIZED_NAME_NOTE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.sanitizedNameHelpText', - { - defaultMessage: 'Your engine will be named', - } -); - -export const ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.engineName.placeholder', - { - defaultMessage: 'i.e., my-search-engine', - } -); - -export const ENGINE_CREATION_FORM_ENGINE_LANGUAGE_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.engineLanguage.label', - { - defaultMessage: 'Engine language', - } -); - -export const ENGINE_CREATION_FORM_SUBMIT_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.submitButton.buttonLabel', - { - defaultMessage: 'Create engine', - } -); - -export const ENGINE_CREATION_SUCCESS_MESSAGE = (name: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.engineCreation.successMessage', { - defaultMessage: "Engine ''{name}'' was created", - values: { name }, - }); - -export const SUPPORTED_LANGUAGES = [ - { - value: 'Universal', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.universalDropDownOptionLabel', - { - defaultMessage: 'Universal', - } - ), - }, - { - text: '—', - disabled: true, - }, - { - value: 'zh', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.chineseDropDownOptionLabel', - { - defaultMessage: 'Chinese', - } - ), - }, - { - value: 'da', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.danishDropDownOptionLabel', - { - defaultMessage: 'Danish', - } - ), - }, - { - value: 'nl', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.dutchDropDownOptionLabel', - { - defaultMessage: 'Dutch', - } - ), - }, - { - value: 'en', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.englishDropDownOptionLabel', - { - defaultMessage: 'English', - } - ), - }, - { - value: 'fr', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.frenchDropDownOptionLabel', - { - defaultMessage: 'French', - } - ), - }, - { - value: 'de', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.germanDropDownOptionLabel', - { - defaultMessage: 'German', - } - ), - }, - { - value: 'it', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.italianDropDownOptionLabel', - { - defaultMessage: 'Italian', - } - ), - }, - { - value: 'ja', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.japaneseDropDownOptionLabel', - { - defaultMessage: 'Japanese', - } - ), - }, - { - value: 'ko', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.koreanDropDownOptionLabel', - { - defaultMessage: 'Korean', - } - ), - }, - { - value: 'pt', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseDropDownOptionLabel', - { - defaultMessage: 'Portuguese', - } - ), - }, - { - value: 'pt-br', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.portugueseBrazilDropDownOptionLabel', - { - defaultMessage: 'Portuguese (Brazil)', - } - ), - }, - { - value: 'ru', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.russianDropDownOptionLabel', - { - defaultMessage: 'Russian', - } - ), - }, - { - value: 'es', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.spanishDropDownOptionLabel', - { - defaultMessage: 'Spanish', - } - ), - }, - { - value: 'th', - text: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.supportedLanguages.thaiDropDownOptionLabel', - { - defaultMessage: 'Thai', - } - ), - }, -]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.scss deleted file mode 100644 index 074b79503c03c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.scss +++ /dev/null @@ -1,20 +0,0 @@ -.entSearch__createEngineLayout { - max-width: 60rem; - margin: auto; -} - -.entSearch__indexListItem { - pointer-events: none; - .euiBadge { - pointer-events: all; - } -} - -.entSearch__indexSelectable { - .euiSelectableListItem-isFocused, - .euiSelectableListItem__append { - position: absolute; - top: $euiSizeXS; - right: calc(1rem - 2px); - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.test.tsx deleted file mode 100644 index 121331456f595..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; -import { useLocation } from 'react-router-dom'; - -import { shallow } from 'enzyme'; - -import { ConfigureAppSearchEngine } from './configure_app_search_engine'; -import { ConfigureElasticsearchEngine } from './configure_elasticsearch_engine'; -import { EngineCreationSteps } from './engine_creation_logic'; -import { ReviewElasticsearchEngine } from './review_elasticsearch_engine'; -import { SelectEngineType } from './select_engine_type'; - -import { EngineCreation } from '.'; - -const MOCK_ACTIONS = { - setIngestionMethod: jest.fn(), -}; - -describe('EngineCreation', () => { - beforeEach(() => { - jest.clearAllMocks(); - - setMockActions(MOCK_ACTIONS); - }); - - it('renders SelectEngineType when the EngineCreationSteps is SelectStep', () => { - setMockValues({ currentEngineCreationStep: EngineCreationSteps.SelectStep }); - - const wrapper = shallow(); - expect(wrapper.find(SelectEngineType)).toHaveLength(1); - }); - - it('renders ConfigureAppSearchEngine when appropriate', () => { - setMockValues({ - currentEngineCreationStep: EngineCreationSteps.ConfigureStep, - engineType: 'appSearch', - }); - - const wrapper = shallow(); - expect(wrapper.find(ConfigureAppSearchEngine)).toHaveLength(1); - }); - - it('renders ConfigureElasticsearchEngine when appropriate', () => { - setMockValues({ - currentEngineCreationStep: EngineCreationSteps.ConfigureStep, - engineType: 'elasticsearch', - }); - - const wrapper = shallow(); - expect(wrapper.find(ConfigureElasticsearchEngine)).toHaveLength(1); - }); - - it('renders ReviewElasticsearchEngine when the EngineCreationSteps is ReviewStep', () => { - setMockValues({ currentEngineCreationStep: EngineCreationSteps.ReviewStep }); - - const wrapper = shallow(); - expect(wrapper.find(ReviewElasticsearchEngine)).toHaveLength(1); - }); - - it('EngineCreationLanguageInput calls setIngestionMethod on mount', () => { - const search = '?method=crawler'; - (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); - - shallow(); - expect(MOCK_ACTIONS.setIngestionMethod).toHaveBeenCalledWith('crawler'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx deleted file mode 100644 index b9fd23ac904f0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useLocation } from 'react-router-dom'; - -import { Location } from 'history'; -import { useActions, useValues } from 'kea'; - -import { ESINDEX_QUERY_PARAMETER } from '../../../shared/constants'; -import { parseQueryParams } from '../../../shared/query_params'; -import { ENGINES_TITLE } from '../engines'; -import { AppSearchPageTemplate } from '../layout'; - -import { ConfigureAppSearchEngine } from './configure_app_search_engine'; -import { ConfigureElasticsearchEngine } from './configure_elasticsearch_engine'; -import { ENGINE_CREATION_TITLE } from './constants'; -import { EngineCreationLogic, EngineCreationSteps } from './engine_creation_logic'; -import { ReviewElasticsearchEngine } from './review_elasticsearch_engine'; -import { SelectEngineType } from './select_engine_type'; - -export const EngineCreation: React.FC = () => { - const { search } = useLocation() as Location; - const { method, ...params } = parseQueryParams(search); - - const { engineType, currentEngineCreationStep } = useValues(EngineCreationLogic); - const { setIngestionMethod, initializeWithESIndex } = useActions(EngineCreationLogic); - - useEffect(() => { - if (typeof method === 'string') { - setIngestionMethod(method); - } - const esIndexParam = params[ESINDEX_QUERY_PARAMETER]; - if (typeof esIndexParam === 'string') { - initializeWithESIndex(esIndexParam); - } - }, []); - - return ( - - {currentEngineCreationStep === EngineCreationSteps.SelectStep && } - {currentEngineCreationStep === EngineCreationSteps.ConfigureStep && - engineType === 'appSearch' && } - {currentEngineCreationStep === EngineCreationSteps.ConfigureStep && - engineType === 'elasticsearch' && } - {currentEngineCreationStep === EngineCreationSteps.ReviewStep && ( - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts deleted file mode 100644 index 9a5754173db1b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts +++ /dev/null @@ -1,620 +0,0 @@ -/* - * 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, - mockHttpValues, - mockKibanaValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; - -import { - DEFAULT_VALUES, - mockElasticsearchIndices, - mockSearchIndexOptions, -} from '../../__mocks__/engine_creation_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { EngineCreationLogic, EngineCreationSteps } from './engine_creation_logic'; - -describe('EngineCreationLogic', () => { - const { mount } = new LogicMounter(EngineCreationLogic); - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; - - it('has expected default values', () => { - mount(); - expect(EngineCreationLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('setIngestionMethod', () => { - it('sets ingestion method to the provided value', () => { - mount(); - EngineCreationLogic.actions.setIngestionMethod('crawler'); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - ingestionMethod: 'crawler', - }); - }); - }); - - describe('setLanguage', () => { - it('sets language to the provided value', () => { - mount(); - EngineCreationLogic.actions.setLanguage('English'); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - language: 'English', - }); - }); - }); - - describe('setRawName', () => { - beforeAll(() => { - mount(); - EngineCreationLogic.actions.setRawName('Name__With#$&*%Special--Characters'); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('should set rawName to provided value', () => { - expect(EngineCreationLogic.values.rawName).toEqual('Name__With#$&*%Special--Characters'); - }); - - it('should set name to a sanitized value', () => { - expect(EngineCreationLogic.values.name).toEqual('name-with-special-characters'); - }); - }); - - describe('submitEngine', () => { - it('sets isLoading to true', () => { - mount({ isLoading: false }); - EngineCreationLogic.actions.submitEngine(); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLoading: true, - }); - }); - }); - - describe('onSubmitError', () => { - it('resets isLoading to false', () => { - mount({ isLoading: true }); - EngineCreationLogic.actions.onSubmitError(); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLoading: false, - }); - }); - - it('resets selectedIndex', () => { - mount({ selectedIndex: 'search-selected-index' }); - EngineCreationLogic.actions.onSubmitError(); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - }); - }); - }); - - describe('loadIndices', () => { - it('sets isLoadingIndices to true', () => { - mount({ isLoadingIndices: false }); - EngineCreationLogic.actions.loadIndices(); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLoadingIndices: true, - }); - }); - }); - - describe('onLoadIndicesSuccess', () => { - it('sets isLoadingIndices to false', () => { - mount({ isLoadingIndices: true }); - EngineCreationLogic.actions.onLoadIndicesSuccess([]); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLoadingIndices: false, - }); - }); - }); - - describe('setSelectedIndex', () => { - it('sets selected index name', () => { - mount(); - EngineCreationLogic.actions.setSelectedIndex('search-test-index'); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - selectedIndex: 'search-test-index', - isAliasRequired: false, - }); - }); - - it('sets aliasRawName and isAliasRequired if it does not start with "search-"', () => { - mount(); - EngineCreationLogic.actions.setSelectedIndex('test-index'); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - selectedIndex: 'test-index', - aliasName: 'search-test-index-alias', - aliasRawName: 'search-test-index-alias', - isAliasRequired: true, - }); - }); - }); - - describe('setEngineType', () => { - it('sets engine type', () => { - mount(); - EngineCreationLogic.actions.setEngineType('elasticsearch'); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - engineType: 'elasticsearch', - }); - }); - }); - - describe('setAliasRawName', () => { - it('sets aliasRawName', () => { - mount(); - EngineCreationLogic.actions.setAliasRawName('search index name'); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - aliasRawName: 'search index name', - aliasName: 'search-index-name', - }); - }); - }); - - describe('setCreationStep', () => { - it('sets currentEngineCreationStep', () => { - mount(); - EngineCreationLogic.actions.setCreationStep(EngineCreationSteps.ConfigureStep); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - currentEngineCreationStep: EngineCreationSteps.ConfigureStep, - }); - }); - }); - - describe('setIsAliasAllowed', () => { - it('sets isAliasAllowed', () => { - mount(); - EngineCreationLogic.actions.setIsAliasAllowed(false); - expect(EngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isAliasAllowed: false, - }); - }); - }); - }); - - describe('selectors', () => { - beforeEach(() => { - mount(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('indicesFormatted', () => { - it('should return empty array when no index available', () => { - expect(EngineCreationLogic.values.indices).toEqual([]); - expect(EngineCreationLogic.values.indicesFormatted).toEqual([]); - }); - - it('should return SearchIndexSelectableOption list calculated from indices', () => { - mount({ - indices: mockElasticsearchIndices, - }); - - expect(EngineCreationLogic.values.indicesFormatted).toEqual(mockSearchIndexOptions); - }); - - it('should handle checked condition correctly', () => { - mount({ - indices: mockElasticsearchIndices, - selectedIndex: 'search-my-index-1', - }); - const mockCheckedSearchIndexOptions = [...mockSearchIndexOptions]; - mockCheckedSearchIndexOptions[0].checked = 'on'; - - expect(EngineCreationLogic.values.indicesFormatted).toEqual(mockCheckedSearchIndexOptions); - }); - }); - - describe('isSubmitDisabled', () => { - describe('App Search based engine', () => { - it('should disable button if engine name is empty', () => { - mount({ - rawName: '', - engineType: 'appSearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(true); - }); - it('should enable button if engine name is entered', () => { - mount({ - rawName: 'my-engine-name', - engineType: 'appSearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(false); - }); - }); - - describe('Elasticsearch Index based engine', () => { - it('should disable button if engine name is empty', () => { - mount({ - rawName: '', - selectedIndex: 'search-my-index-1', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(true); - }); - - it('should disable button if no index selected', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: '', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(true); - }); - - it('should disable button if non "search-" index selected without alias', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: 'my-index', - aliasRawName: '', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(true); - }); - - it('should disable button if non "search-" index selected and non "search-" alias', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: 'my-index', - aliasRawName: 'an-alias', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(true); - }); - - it('should disable button if "search-" index selected and non "search-" alias', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: 'search-my-index', - aliasRawName: 'an-alias', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(true); - }); - - it('should enable button if all selected and "search-" index', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: 'search-my-index-1', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(false); - }); - - it('should enable button if all selected and "search-" alias', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: 'my-index-1', - aliasRawName: 'search-my-index-1', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(false); - }); - - it('should enable button if all selected and "search-" index and alias', () => { - mount({ - rawName: 'my-engine-name', - selectedIndex: 'my-index-1', - aliasRawName: 'search-my-index-1', - engineType: 'elasticsearch', - }); - - expect(EngineCreationLogic.values.isSubmitDisabled).toBe(false); - }); - }); - }); - - describe('isAliasRequired', () => { - it('should return false when index is prefixed with "search-"', () => { - mount({ - selectedIndex: 'search-my-index', - }); - - expect(EngineCreationLogic.values.isAliasRequired).toEqual(false); - }); - - it('should return false when index is not selected', () => { - mount({ - selectedIndex: '', - }); - - expect(EngineCreationLogic.values.isAliasRequired).toEqual(false); - }); - - it('should return true when index is not prefixed with "search-"', () => { - mount({ - selectedIndex: 'my-index', - }); - - expect(EngineCreationLogic.values.isAliasRequired).toEqual(true); - }); - }); - - describe('selectedIndexFormatted', () => { - it('should be null if indices is empty', () => { - mount({ - indices: [], - }); - - expect(EngineCreationLogic.values.selectedIndexFormatted).toBeUndefined(); - }); - - it('should be null if there is no selectedIndex', () => { - mount({ - indices: mockElasticsearchIndices, - selectedIndex: '', - }); - - expect(EngineCreationLogic.values.selectedIndexFormatted).toBeUndefined(); - }); - - it('should select the correctly formatted search index', () => { - mount({ - indices: mockElasticsearchIndices, - selectedIndex: 'search-my-index-2', - }); - const mockCheckedSearchIndexOptions = [...mockSearchIndexOptions]; - mockCheckedSearchIndexOptions[2].checked = 'on'; - - expect(EngineCreationLogic.values.selectedIndexFormatted).toEqual( - mockCheckedSearchIndexOptions[2] - ); - }); - }); - - describe('aliasName', () => { - it('should format the aliasName by replacing whitespace with hyphens from the aliasRawName', () => { - mount({ - aliasRawName: ' search my index-------now', - }); - - expect(EngineCreationLogic.values.aliasName).toEqual('search-my-index-now'); - }); - }); - - describe('aliasNameErrorMessage', () => { - it('should be an empty string if indices is empty', () => { - mount({ - indices: [], - }); - - expect(EngineCreationLogic.values.aliasNameErrorMessage).toEqual(''); - }); - - it('should be an empty string if there is no aliasName', () => { - mount({ - aliasName: '', - indices: mockElasticsearchIndices, - }); - - expect(EngineCreationLogic.values.aliasNameErrorMessage).toEqual(''); - }); - - it('should set an error message if there is an existing alias/index', () => { - mount({ - aliasRawName: 'alias-without-manage-privilege', - indices: mockElasticsearchIndices, - }); - - // ugly, but cannot use dedent here and pass Kibana's Checks - expect(EngineCreationLogic.values.aliasNameErrorMessage).toEqual(` -There is an existing index or alias with the name alias-without-manage-privilege. -Please choose another alias name. -`); - }); - }); - - describe('showAliasNameErrorMessages', () => { - it('should be false if there is no error message', () => { - mount({ - aliasNameErrorMessage: '', - }); - - expect(EngineCreationLogic.values.showAliasNameErrorMessages).toBe(false); - }); - - it('should be true if there is an error message', () => { - mount({ - aliasRawName: 'alias-without-manage-privilege', - indices: mockElasticsearchIndices, - }); - - expect(EngineCreationLogic.values.showAliasNameErrorMessages).toBe(true); - }); - }); - }); - - describe('listeners', () => { - describe('onEngineCreationSuccess', () => { - beforeAll(() => { - mount({ language: 'English', rawName: 'test' }); - EngineCreationLogic.actions.onEngineCreationSuccess(); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('should show a success message', () => { - expect(flashSuccessToast).toHaveBeenCalledWith("Engine 'test' was created"); - }); - - it('should navigate the user to the engine page', () => { - expect(navigateToUrl).toHaveBeenCalledWith('/engines/test'); - }); - }); - - describe('submitEngine', () => { - describe('Indexed engine', () => { - beforeAll(() => { - mount({ language: 'English', rawName: 'test' }); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('POSTS to /internal/app_search/engines', () => { - const body = JSON.stringify({ - name: EngineCreationLogic.values.name, - language: EngineCreationLogic.values.language, - }); - EngineCreationLogic.actions.submitEngine(); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines', { body }); - }); - - it('calls onEngineCreationSuccess on valid submission', async () => { - jest.spyOn(EngineCreationLogic.actions, 'onEngineCreationSuccess'); - http.post.mockReturnValueOnce(Promise.resolve({})); - EngineCreationLogic.actions.submitEngine(); - await nextTick(); - expect(EngineCreationLogic.actions.onEngineCreationSuccess).toHaveBeenCalledTimes(1); - }); - - it('calls flashAPIErrors on API Error', async () => { - http.post.mockReturnValueOnce(Promise.reject()); - EngineCreationLogic.actions.submitEngine(); - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - }); - }); - - describe('Elasticsearch index based engine', () => { - beforeEach(() => { - mount({ - engineType: 'elasticsearch', - name: 'engine-name', - selectedIndex: 'search-selected-index', - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('POSTS to /internal/app_search/elasticsearch/engines', () => { - const body = JSON.stringify({ - name: EngineCreationLogic.values.name, - search_index: { - type: 'elasticsearch', - index_name: EngineCreationLogic.values.selectedIndex, - }, - }); - EngineCreationLogic.actions.submitEngine(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/elasticsearch/engines', { - body, - }); - }); - - it('calls onEngineCreationSuccess on valid submission', async () => { - jest.spyOn(EngineCreationLogic.actions, 'onEngineCreationSuccess'); - http.post.mockReturnValueOnce(Promise.resolve({})); - EngineCreationLogic.actions.submitEngine(); - await nextTick(); - expect(EngineCreationLogic.actions.onEngineCreationSuccess).toHaveBeenCalledTimes(1); - }); - - it('calls flashAPIErrors on API Error', async () => { - http.post.mockReturnValueOnce(Promise.reject()); - EngineCreationLogic.actions.submitEngine(); - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - }); - - it('adds alias_name to the payload if aliasName is present', () => { - mount({ - engineType: 'elasticsearch', - name: 'engine-name', - selectedIndex: 'selected-index', - aliasRawName: 'search-selected-index', - }); - - const body = JSON.stringify({ - name: EngineCreationLogic.values.name, - search_index: { - type: 'elasticsearch', - index_name: EngineCreationLogic.values.selectedIndex, - alias_name: EngineCreationLogic.values.aliasName, - }, - }); - EngineCreationLogic.actions.submitEngine(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/elasticsearch/engines', { - body, - }); - }); - }); - }); - - describe('loadIndices', () => { - beforeEach(() => { - mount(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('GETs to /internal/enterprise_search/search_indices', () => { - EngineCreationLogic.actions.loadIndices(); - expect(http.get).toHaveBeenCalledWith('/internal/enterprise_search/search_indices'); - }); - - it('calls onLoadIndicesSuccess with payload on load is successful', async () => { - jest.spyOn(EngineCreationLogic.actions, 'onLoadIndicesSuccess'); - http.get.mockReturnValueOnce(Promise.resolve([mockElasticsearchIndices[0]])); - EngineCreationLogic.actions.loadIndices(); - await nextTick(); - expect(EngineCreationLogic.actions.onLoadIndicesSuccess).toHaveBeenCalledWith([ - mockElasticsearchIndices[0], - ]); - }); - - it('calls flashAPIErros on indices load fails', async () => { - jest.spyOn(EngineCreationLogic.actions, 'onSubmitError'); - http.get.mockRejectedValueOnce({}); - EngineCreationLogic.actions.loadIndices(); - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - expect(EngineCreationLogic.actions.onSubmitError).toHaveBeenCalledTimes(1); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.ts deleted file mode 100644 index 63fbb97153a8e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.ts +++ /dev/null @@ -1,309 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { ElasticsearchIndexWithPrivileges } from '../../../../../common/types'; -import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; -import { formatApiName } from '../../utils/format_api_name'; - -import { DEFAULT_LANGUAGE, ENGINE_CREATION_SUCCESS_MESSAGE } from './constants'; -import { SearchIndexSelectableOption } from './search_index_selectable'; -import { getRedirectToAfterEngineCreation, formatIndicesToSelectable } from './utils'; - -export enum EngineCreationSteps { - SelectStep = 'Select Engine Type', - ConfigureStep = 'Configure Engine', - ReviewStep = 'Review', -} - -export type EngineType = 'appSearch' | 'elasticsearch'; -interface EngineCreationActions { - onEngineCreationSuccess(): void; - setIngestionMethod(method: string): { method: string }; - setLanguage(language: string): { language: string }; - setRawName(rawName: string): { rawName: string }; - setAliasRawName(aliasRawName: string): { aliasRawName: string }; - setCreationStep(creationStep: EngineCreationSteps): EngineCreationSteps; - submitEngine(): void; - onSubmitError(): void; - loadIndices(): void; - onLoadIndicesSuccess(indices: ElasticsearchIndexWithPrivileges[]): { - indices: ElasticsearchIndexWithPrivileges[]; - }; - setSelectedIndex(selectedIndexName: string): { selectedIndexName: string }; - setEngineType(engineType: EngineType): { engineType: EngineType }; - setIsAliasAllowed(isAliasAllowed: boolean): { isAliasAllowed: boolean }; - initializeWithESIndex(indexName: string): { indexName: string }; -} - -interface EngineCreationValues { - currentEngineCreationStep: EngineCreationSteps; - ingestionMethod: string; - isLoading: boolean; - language: string; - name: string; - rawName: string; - isLoadingIndices: boolean; - indices: ElasticsearchIndexWithPrivileges[]; - indicesFormatted: SearchIndexSelectableOption[]; - selectedIndex: string; - selectedIndexFormatted?: SearchIndexSelectableOption; - engineType: EngineType; - aliasName: string; - aliasNameErrorMessage: string; - aliasRawName: string; - showAliasNameErrorMessages: boolean; - isAliasAllowed: boolean; - isAliasRequired: boolean; - isSubmitDisabled: boolean; -} - -export const EngineCreationLogic = kea>({ - path: ['enterprise_search', 'app_search', 'engine_creation_logic'], - actions: { - onEngineCreationSuccess: true, - setIngestionMethod: (method) => ({ method }), - setLanguage: (language) => ({ language }), - setRawName: (rawName) => ({ rawName }), - setAliasRawName: (aliasRawName) => ({ aliasRawName }), - submitEngine: true, - onSubmitError: true, - loadIndices: true, - onLoadIndicesSuccess: (indices) => ({ indices }), - setSelectedIndex: (selectedIndexName) => ({ selectedIndexName }), - setEngineType: (engineType) => ({ engineType }), - setCreationStep: (currentEngineCreationStep) => currentEngineCreationStep, - setIsAliasAllowed: (isAliasAllowed) => ({ isAliasAllowed }), - initializeWithESIndex: (indexName) => ({ indexName }), - }, - reducers: { - ingestionMethod: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setIngestionMethod: (_, { method }) => method, - }, - ], - isLoading: [ - false, - { - submitEngine: () => true, - onSubmitError: () => false, - }, - ], - language: [ - DEFAULT_LANGUAGE, - { - // @ts-expect-error upgrade typescript v5.1.6 - setLanguage: (_, { language }) => language, - }, - ], - rawName: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setRawName: (_, { rawName }) => rawName, - }, - ], - aliasRawName: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setAliasRawName: (_, { aliasRawName }) => aliasRawName, - // @ts-expect-error upgrade typescript v5.1.6 - setSelectedIndex: (_, { selectedIndexName }) => { - return selectedIndexName.length === 0 || selectedIndexName.startsWith('search-') - ? '' - : `search-${selectedIndexName}-alias`; - }, - // @ts-expect-error upgrade typescript v5.1.6 - initializeWithESIndex: (_, { indexName }) => - indexName.length === 0 || indexName.startsWith('search-') - ? '' - : `search-${indexName}-alias`, - }, - ], - isAliasAllowed: [ - true, - { - // @ts-expect-error upgrade typescript v5.1.6 - setIsAliasAllowed: (_, { isAliasAllowed }) => isAliasAllowed, - }, - ], - isLoadingIndices: [ - false, - { - loadIndices: () => true, - onLoadIndicesSuccess: () => false, - onSubmitError: () => false, - }, - ], - indices: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onLoadIndicesSuccess: (_, { indices }) => indices, - }, - ], - selectedIndex: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setSelectedIndex: (_, { selectedIndexName }) => selectedIndexName, - onSubmitError: () => '', - // @ts-expect-error upgrade typescript v5.1.6 - initializeWithESIndex: (_, { indexName }) => indexName, - }, - ], - engineType: [ - 'appSearch', - { - // @ts-expect-error upgrade typescript v5.1.6 - setEngineType: (_, { engineType }) => engineType, - // @ts-expect-error upgrade typescript v5.1.6 - initializeWithESIndex: () => 'elasticsearch', - }, - ], - currentEngineCreationStep: [ - EngineCreationSteps.SelectStep, - { - // @ts-expect-error upgrade typescript v5.1.6 - setCreationStep: (_, currentEngineCreationStep) => currentEngineCreationStep, - initializeWithESIndex: () => EngineCreationSteps.ConfigureStep, - }, - ], - }, - selectors: ({ selectors }) => ({ - name: [() => [selectors.rawName], (rawName) => formatApiName(rawName)], - aliasName: [() => [selectors.aliasRawName], (aliasRawName) => formatApiName(aliasRawName)], - indicesFormatted: [ - () => [selectors.indices, selectors.selectedIndex], - (indices: ElasticsearchIndexWithPrivileges[], selectedIndexName) => - formatIndicesToSelectable(indices, selectedIndexName), - ], - isSubmitDisabled: [ - () => [ - selectors.name, - selectors.engineType, - selectors.selectedIndex, - selectors.aliasName, - selectors.showAliasNameErrorMessages, - ], - ( - name: string, - engineType: EngineType, - selectedIndex: string, - aliasName: string, - showAliasNameErrorMessages: boolean - ) => { - if (name.length === 0 || showAliasNameErrorMessages) { - return true; - } - - if (engineType === 'elasticsearch') { - if (selectedIndex.length === 0) { - return true; - } - - if (aliasName.length === 0) { - return !selectedIndex.startsWith('search-'); - } else { - return !aliasName.startsWith('search-'); - } - } - - return false; - }, - ], - isAliasRequired: [ - () => [selectors.selectedIndex], - (selectedIndex: string) => selectedIndex.length > 0 && !selectedIndex?.startsWith('search-'), - ], - selectedIndexFormatted: [ - () => [selectors.selectedIndex, selectors.indicesFormatted], - (selectedIndex: string, indicesFormatted: SearchIndexSelectableOption[]) => { - return indicesFormatted.find((el) => el.label === selectedIndex); - }, - ], - aliasNameErrorMessage: [ - () => [selectors.aliasName, selectors.indices], - (aliasName: string, indices: ElasticsearchIndexWithPrivileges[]) => { - const existingAlias = indices.find((el) => el.name === aliasName); - if (existingAlias) { - return i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.aliasName.errorText', - { - // ugly, but cannot use dedent here and pass Kibana's Checks - defaultMessage: ` -There is an existing index or alias with the name {aliasName}. -Please choose another alias name. -`, - values: { aliasName }, - } - ); - } else { - return ''; - } - }, - ], - showAliasNameErrorMessages: [ - () => [selectors.aliasNameErrorMessage], - (aliasNameErrorMessage: string) => aliasNameErrorMessage.length > 0, - ], - }), - listeners: ({ values, actions }) => ({ - submitEngine: async () => { - const { http } = HttpLogic.values; - const { name, language, engineType, selectedIndex, aliasName } = values; - - try { - if (engineType === 'appSearch') { - const body = JSON.stringify({ name, language }); - - await http.post('/internal/app_search/engines', { body }); - } else { - const body = JSON.stringify({ - name, - search_index: { - type: 'elasticsearch', - index_name: selectedIndex, - ...(aliasName.length === 0 ? {} : { alias_name: aliasName }), - }, - }); - await http.post('/internal/app_search/elasticsearch/engines', { body }); - } - actions.onEngineCreationSuccess(); - } catch (e) { - flashAPIErrors(e); - actions.onSubmitError(); - } - }, - onEngineCreationSuccess: () => { - const { ingestionMethod, name } = values; - const { navigateToUrl } = KibanaLogic.values; - const toUrl = getRedirectToAfterEngineCreation({ ingestionMethod, engineName: name }); - - flashSuccessToast(ENGINE_CREATION_SUCCESS_MESSAGE(name)); - navigateToUrl(toUrl); - }, - loadIndices: async () => { - const { http } = HttpLogic.values; - try { - const indices = await http.get('/internal/enterprise_search/search_indices'); - actions.onLoadIndicesSuccess(indices as ElasticsearchIndexWithPrivileges[]); - } catch (e) { - flashAPIErrors(e); - actions.onSubmitError(); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/index.ts deleted file mode 100644 index a1770cc50ea93..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { EngineCreation } from './engine_creation'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/review_elasticsearch_engine.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/review_elasticsearch_engine.test.tsx deleted file mode 100644 index 40916bc4241a0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/review_elasticsearch_engine.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { ReviewElasticsearchEngine } from './review_elasticsearch_engine'; - -describe('ReviewElasticsearchEngine', () => { - const DEFAULT_VALUES = { - aliasName: '', - }; - - const MOCK_ACTIONS = { - submitEngine: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ElasticsearchEngineCreationForm"]')).toHaveLength(1); - }); - - it('ElasticsearchEngineCreationForm calls submitEngine on form submit', () => { - const wrapper = shallow(); - const simulatedEvent = { - preventDefault: jest.fn(), - }; - wrapper - .find('[data-test-subj="ElasticsearchEngineCreationForm"]') - .simulate('submit', simulatedEvent); - - expect(MOCK_ACTIONS.submitEngine).toHaveBeenCalledTimes(1); - }); - - it('ElasticsearchEngineCreationFormAliasNameCallout is present if aliasName is present', () => { - setMockValues({ ...DEFAULT_VALUES, aliasName: 'i-exist' }); - - const wrapper = shallow(); - expect( - wrapper.find('[data-test-subj="ElasticsearchEngineCreationFormAliasNameCallout"]') - ).toHaveLength(1); - }); - - it('ElasticsearchEngineCreationFormAliasNameCallout is not present if aliasName is not present', () => { - setMockValues({ ...DEFAULT_VALUES, aliasName: '' }); - - const wrapper = shallow(); - expect( - wrapper.find('[data-test-subj="ElasticsearchEngineCreationFormAliasNameCallout"]') - ).toHaveLength(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/review_elasticsearch_engine.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/review_elasticsearch_engine.tsx deleted file mode 100644 index 5218e4f2c3eec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/review_elasticsearch_engine.tsx +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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, - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiI18n, - EuiPanel, - EuiSpacer, - EuiStepsHorizontal, - EuiText, - EuiTextAlign, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { EngineCreationLogic, EngineCreationSteps } from './engine_creation_logic'; -import { IndexStatusDetails } from './search_index_selectable'; - -export const ReviewElasticsearchEngine: React.FC = () => { - const { aliasName, name, selectedIndex, selectedIndexFormatted } = useValues(EngineCreationLogic); - const { setCreationStep, submitEngine } = useActions(EngineCreationLogic); - - return ( -
    - setCreationStep(EngineCreationSteps.SelectStep), - status: 'complete', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label', - { - defaultMessage: 'Search engine type', - } - ), - }, - { - onClick: () => setCreationStep(EngineCreationSteps.ConfigureStep), - status: 'complete', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label', - { - defaultMessage: 'Configuration', - } - ), - }, - { - onClick: () => {}, - status: 'current', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.review.label', - { - defaultMessage: 'Review', - } - ), - }, - ]} - /> - - - - - { - e.preventDefault(); - submitEngine(); - }} - > - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.title.label', - { - defaultMessage: 'Review your search engine', - } - )} -

    -
    -
    - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.description', - { - defaultMessage: - 'Your App Search engine will be created with the following configuration.', - } - )} - - - - - - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.title', - { - defaultMessage: 'Engine Type', - } - )} -

    -
    - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineType.description', - { - defaultMessage: 'Elasticsearch index-based', - } - )} - -
    -
    - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.aliasName.title', - { - defaultMessage: 'Alias Name', - } - )} -

    -
    - {aliasName || '--'} -
    -
    - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.engineName.title', - { - defaultMessage: 'Engine Name', - } - )} -

    -
    - {name} -
    -
    -
    - - - - - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.reviewForm.elasticsearchIndex.title', - { - defaultMessage: 'Elasticsearch Index', - } - )} -

    -
    - {selectedIndex} - - - - -
    -
    -
    - - - - {aliasName.length > 0 && ( - - {aliasName}, - name: {name}, - }} - /> - - )} - - - - - - { - setCreationStep(EngineCreationSteps.ConfigureStep); - }} - > - {i18n.translate( - // FIXME: this and the rest of the paths - 'xpack.enterpriseSearch.appSearch.engineCreation.form.editConfiguration.label', - { - defaultMessage: 'Edit configuration', - } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.form.continue.label', - { - defaultMessage: 'Create search engine', - } - )} - - - -
    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/search_index_selectable.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/search_index_selectable.test.tsx deleted file mode 100644 index b798b71e305e5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/search_index_selectable.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { IndexStatusDetails, SearchIndexSelectableOption } from './search_index_selectable'; - -const mockOption: SearchIndexSelectableOption = { - count: 123, - label: 'string', - alias: true, - badge: { - color: 'string', - label: 'string', - toolTipTitle: 'string', - toolTipContent: 'string', - }, - disabled: true, - total: { - docs: { - count: 123, - deleted: 123, - }, - store: { - size_in_bytes: 'string', - }, - }, -}; - -describe('IndexStatusDetails', () => { - it('does not render anything if an option is not provided', () => { - const wrapper = shallow(); - expect(wrapper.find('.entSearch__indexListItem')).toHaveLength(0); - }); - - it('renders if there is an option provided', () => { - const wrapper = shallow(); - expect(wrapper.find('.entSearch__indexListItem')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/search_index_selectable.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/search_index_selectable.tsx deleted file mode 100644 index 22add9bdaa518..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/search_index_selectable.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 { HealthStatus } from '@elastic/elasticsearch/lib/api/types'; - -import { - EuiTextColor, - EuiFlexGroup, - EuiFlexItem, - EuiHealth, - EuiToolTip, - EuiBadge, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import './engine_creation.scss'; -import { healthColorsMapSelectable } from '../../../shared/constants/health_colors'; - -export interface SearchIndexSelectableOption { - label: string; - health?: HealthStatus; - status?: string; - alias: boolean; - badge: { - color: string; - icon?: string; - label: string; - toolTipTitle: string; - toolTipContent: string; - }; - disabled: boolean; - total: { - docs: { - count: number; - deleted: number; - }; - store: { - size_in_bytes: string; - }; - }; - checked?: 'on'; - count: number; -} -interface IndexStatusDetailsProps { - option?: SearchIndexSelectableOption; -} - -export const IndexStatusDetails: React.FC = ({ option }) => { - return !option ? ( - <> - ) : ( - - - - {option.health ?? '-'} - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.status', - { defaultMessage: 'Status' } - )} - - : {option.status ?? '-'} - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.docCount', - { defaultMessage: 'Docs count' } - )} - - : {option.count ?? '-'} - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.configureForm.elasticsearchIndex.storage', - { defaultMessage: 'Storage size' } - )} - - : - -  {option.total?.store?.size_in_bytes ?? '-'} - - - - - - -

    {option.badge.label}

    -
    -
    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/select_engine_type.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/select_engine_type.test.tsx deleted file mode 100644 index 0a289a7425171..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/select_engine_type.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SelectEngineType } from './select_engine_type'; - -describe('SelectEngineType', () => { - const DEFAULT_VALUES = { - engineType: 'appSearch', - }; - - const MOCK_ACTIONS = { - setEngineType: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="AppSearchEngineSelectable"]')).toHaveLength(1); - expect(wrapper.find('[data-test-subj="ElasticsearchEngineSelectable"]')).toHaveLength(1); - }); - - it('create App Search managed engine is the default', () => { - const wrapper = shallow(); - const appSearchCardSelectable = wrapper - .find('[data-test-subj="AppSearchEngineSelectable"]') - .prop('selectable') as any; - const elasticsearchCardSelectable = wrapper - .find('[data-test-subj="ElasticsearchEngineSelectable"]') - .prop('selectable') as any; - - expect(appSearchCardSelectable.isSelected).toBeTruthy(); - expect(elasticsearchCardSelectable.isSelected).toBeFalsy(); - }); - - it('create Elasticsearch-index based engine is selected if chosen', () => { - setMockValues({ - ...DEFAULT_VALUES, - engineType: 'elasticsearch', - }); - - const wrapper = shallow(); - const appSearchCardSelectable = wrapper - .find('[data-test-subj="AppSearchEngineSelectable"]') - .prop('selectable') as any; - const elasticsearchCardSelectable = wrapper - .find('[data-test-subj="ElasticsearchEngineSelectable"]') - .prop('selectable') as any; - - expect(appSearchCardSelectable.isSelected).toBeFalsy(); - expect(elasticsearchCardSelectable.isSelected).toBeTruthy(); - }); - - it('clicking on Elasticsearch-index based engine sets the engineType', () => { - const wrapper = shallow(); - const elasticsearchCardSelectable = wrapper - .find('[data-test-subj="ElasticsearchEngineSelectable"]') - .prop('selectable') as any; - - // Like .simulate('click'), but get the function from the selectable - // property and then call it directly - elasticsearchCardSelectable.onClick(); - - expect(MOCK_ACTIONS.setEngineType).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/select_engine_type.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/select_engine_type.tsx deleted file mode 100644 index 1482d0fa88bdf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/select_engine_type.tsx +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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, - EuiCard, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiPanel, - EuiSpacer, - EuiStepsHorizontal, - EuiText, - EuiTextAlign, - EuiTitle, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { EngineCreationLogic, EngineCreationSteps } from './engine_creation_logic'; - -export const SelectEngineType: React.FC = () => { - const { engineType } = useValues(EngineCreationLogic); - const { setEngineType, setCreationStep } = useActions(EngineCreationLogic); - - return ( -
    - {}, - status: 'current', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.searchEngineType.label', - { - defaultMessage: 'Search engine type', - } - ), - }, - { - onClick: () => setCreationStep(EngineCreationSteps.ConfigureStep), - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.configuration.label', - { - defaultMessage: 'Configuration', - } - ), - }, - { - onClick: () => {}, - status: 'disabled', - title: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.steps.review.label', - { - defaultMessage: 'Review', - } - ), - }, - ]} - /> - - - - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.title', - { - defaultMessage: 'Select a search engine type', - } - )} -

    -
    -
    - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description', - { - defaultMessage: ` - You can now create search engines that use an existing - Elasticsearch index to combine the search management tools of App - Search with the flexibility of Elasticsearch indices. - `, - } - )} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.selectEngineTypeForm.description.link', - { - defaultMessage: 'Learn more', - } - )} - - - - - - - - - setEngineType('appSearch'), - }} - data-test-subj="AppSearchEngineSelectable" - hasBorder - /> - - - setEngineType('elasticsearch'), - }} - data-test-subj="ElasticsearchEngineSelectable" - hasBorder - /> - - - - - - - - { - setCreationStep(EngineCreationSteps.ConfigureStep); - }} - > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineCreation.nextStep.buttonLabel', - { - defaultMessage: 'Continue', - } - )} - - - -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/utils.test.ts deleted file mode 100644 index 4c8909a87cf53..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/utils.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { getRedirectToAfterEngineCreation } from './utils'; - -describe('getRedirectToAfterEngineCreation', () => { - it('returns crawler path when ingestionMethod is crawler', () => { - const engineName = 'elastic'; - const redirectTo = getRedirectToAfterEngineCreation({ ingestionMethod: 'crawler', engineName }); - expect(redirectTo).toEqual('/engines/elastic/crawler'); - }); - - it('returns engine overview path when there is no ingestionMethod', () => { - const engineName = 'elastic'; - const redirectTo = getRedirectToAfterEngineCreation({ ingestionMethod: '', engineName }); - expect(redirectTo).toEqual('/engines/elastic'); - }); - - it('returns engine overview path with query param when there is ingestionMethod', () => { - const engineName = 'elastic'; - const redirectTo = getRedirectToAfterEngineCreation({ ingestionMethod: 'api', engineName }); - expect(redirectTo).toEqual('/engines/elastic?method=api'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/utils.ts deleted file mode 100644 index 1e4f67e715c21..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/utils.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { generatePath } from 'react-router-dom'; - -import dedent from 'dedent'; - -import { ElasticsearchIndexWithPrivileges } from '../../../../../common/types'; -import { ENGINE_CRAWLER_PATH, ENGINE_PATH } from '../../routes'; - -import { SearchIndexSelectableOption } from './search_index_selectable'; - -export const getRedirectToAfterEngineCreation = ({ - ingestionMethod, - engineName, -}: { - ingestionMethod?: string; - engineName: string; -}): string => { - if (ingestionMethod === 'crawler') { - return generatePath(ENGINE_CRAWLER_PATH, { engineName }); - } - - let enginePath = generatePath(ENGINE_PATH, { engineName }); - if (ingestionMethod) { - enginePath += `?method=${encodeURIComponent(ingestionMethod)}`; - } - - return enginePath; -}; - -export const formatIndicesToSelectable = ( - indices: ElasticsearchIndexWithPrivileges[], - selectedIndexName: string -): SearchIndexSelectableOption[] => { - return indices - .filter(({ alias, privileges }) => { - if (alias) { - return privileges.manage; - } else { - return privileges.read && privileges.manage; - } - }) - .map((index) => { - let icon; - let color; - let toolTipTitle; - let toolTipContent; - - if (index.name.startsWith('search-')) { - color = 'success'; - - if (index.alias) { - toolTipTitle = 'Alias is compatible'; - toolTipContent = 'You can use this alias.'; - } else { - toolTipTitle = 'Index name is compatible'; - toolTipContent = dedent(` - You can directly use this index. You can also optionally create an - alias to use as the source of the engine instead. - `); - } - } else { - if (index.alias) { - icon = 'warning'; - color = 'danger'; - toolTipTitle = 'Alias name is incompatible'; - toolTipContent = 'You\'ll have to create a new alias prefixed with "search-".'; - } else { - icon = 'iInCircle'; - color = 'warning'; - toolTipTitle = 'Index name is incompatible'; - toolTipContent = dedent(` - Enterprise Search will automatically create an alias to use as the - source of the search engine rather than use this index directly. - `); - } - } - - return { - ...(selectedIndexName === index.name ? { checked: 'on' } : {}), - alias: index.alias, - badge: { - color, - toolTipTitle, - toolTipContent, - label: index.alias ? 'Alias' : 'Index', - ...(icon ? { icon } : {}), - }, - count: index.count, - disabled: index.alias && !index.name.startsWith('search-'), - label: index.name, - health: index.health, - status: index.status, - total: index.total, - }; - }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts deleted file mode 100644 index a67ac5066f431..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { TotalStats } from './total_stats'; -export { TotalCharts } from './total_charts'; -export { RecentApiLogs } from './recent_api_logs'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx deleted file mode 100644 index 1281d8f23a7eb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/shallow_useeffect.mock'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { ApiLogsTable } from '../../api_logs'; - -import { RecentApiLogs } from './recent_api_logs'; - -describe('RecentApiLogs', () => { - const actions = { - fetchApiLogs: jest.fn(), - pollForApiLogs: jest.fn(), - }; - - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockActions(actions); - wrapper = shallow(); - }); - - it('renders the recent API logs table', () => { - expect(wrapper.prop('title')).toEqual(

    Recent API events

    ); - expect(wrapper.find(ApiLogsTable)).toHaveLength(1); - }); - - it('calls fetchApiLogs on page load and starts pollForApiLogs', () => { - expect(actions.fetchApiLogs).toHaveBeenCalledTimes(1); - expect(actions.pollForApiLogs).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx deleted file mode 100644 index 18f27c3a1e834..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; -import { ENGINE_API_LOGS_PATH } from '../../../routes'; -import { ApiLogsLogic, ApiLogsTable, NewApiEventsPrompt, ApiLogFlyout } from '../../api_logs'; -import { RECENT_API_EVENTS } from '../../api_logs/constants'; -import { DataPanel } from '../../data_panel'; -import { generateEnginePath } from '../../engine'; - -import { VIEW_API_LOGS } from '../constants'; - -export const RecentApiLogs: React.FC = () => { - const { fetchApiLogs, pollForApiLogs } = useActions(ApiLogsLogic); - - useEffect(() => { - fetchApiLogs(); - pollForApiLogs(); - }, []); - - return ( - {RECENT_API_EVENTS}} - action={ - - - - - - - {VIEW_API_LOGS} - - - - } - hasBorder - > - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx deleted file mode 100644 index f539adda4ccf3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 '../../../__mocks__/engine_logic.mock'; - -import { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { set } from '@kbn/safer-lodash-set/fp'; - -import { SuggestionsCallout } from '../../curations/components/suggestions_callout'; - -import { SuggestedCurationsCallout } from './suggested_curations_callout'; - -const MOCK_VALUES = { - engine: { - adaptive_relevance_suggestions: { - curation: { - pending: 1, - }, - }, - adaptive_relevance_suggestions_active: true, - }, -}; - -describe('SuggestedCurationsCallout', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.is(SuggestionsCallout)); - }); - - it('is empty when the suggestions are undefined', () => { - setMockValues({ - ...MOCK_VALUES, - engine: { - adaptive_relevance_suggestions_active: true, - }, - }); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('is empty when suggestions are not active', () => { - const values = set('engine.adaptive_relevance_suggestions_active', false, MOCK_VALUES); - setMockValues(values); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('is empty when no pending curations', () => { - const values = set('engine.adaptive_relevance_suggestions.curation.pending', 0, MOCK_VALUES); - setMockValues(values); - - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx deleted file mode 100644 index a79c0394fc903..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/suggested_curations_callout.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { i18n } from '@kbn/i18n'; - -import { ENGINE_CURATIONS_PATH } from '../../../routes'; -import { SuggestionsCallout } from '../../curations/components/suggestions_callout'; -import { EngineLogic, generateEnginePath } from '../../engine'; - -export const SuggestedCurationsCallout: React.FC = () => { - const { - engine: { - adaptive_relevance_suggestions: adaptiveRelevanceSuggestions, - adaptive_relevance_suggestions_active: adaptiveRelevanceSuggestionsActive, - }, - } = useValues(EngineLogic); - - const pendingCount = adaptiveRelevanceSuggestions?.curation.pending; - - if ( - typeof adaptiveRelevanceSuggestions === 'undefined' || - pendingCount === 0 || - adaptiveRelevanceSuggestionsActive === false - ) { - return null; - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx deleted file mode 100644 index b20b71487307d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { AnalyticsChart } from '../../analytics'; - -import { TotalCharts } from './total_charts'; - -describe('TotalCharts', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - startDate: '1970-01-01', - queriesPerDay: [0, 1, 2, 3, 5, 10, 50], - operationsPerDay: [0, 0, 0, 0, 0, 0, 0], - }); - wrapper = shallow(); - }); - - it('renders the total queries chart', () => { - const panel = wrapper.find('[data-test-subj="TotalQueriesChart"]'); - - expect(panel.prop('title')).toEqual(

    Total queries

    ); - expect(panel.find(AnalyticsChart)).toHaveLength(1); - }); - - it('renders the total API operations chart', () => { - const panel = wrapper.find('[data-test-subj="TotalApiOperationsChart"]'); - - expect(panel.prop('title')).toEqual(

    Total API operations

    ); - expect(panel.find(AnalyticsChart)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx deleted file mode 100644 index 99990e2b32aea..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { EngineOverviewLogic } from '..'; -import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; -import { ENGINE_ANALYTICS_PATH, ENGINE_API_LOGS_PATH } from '../../../routes'; -import { AnalyticsChart, convertToChartData } from '../../analytics'; -import { TOTAL_QUERIES, TOTAL_API_OPERATIONS } from '../../analytics/constants'; -import { DataPanel } from '../../data_panel'; -import { generateEnginePath } from '../../engine'; - -import { VIEW_ANALYTICS, VIEW_API_LOGS, LAST_7_DAYS } from '../constants'; - -export const TotalCharts: React.FC = () => { - const { startDate, queriesPerDay, operationsPerDay } = useValues(EngineOverviewLogic); - - return ( - - - {TOTAL_QUERIES}} - subtitle={LAST_7_DAYS} - action={ - - {VIEW_ANALYTICS} - - } - hasBorder - > - - - - - {TOTAL_API_OPERATIONS}} - subtitle={LAST_7_DAYS} - action={ - - {VIEW_API_LOGS} - - } - hasBorder - > - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx deleted file mode 100644 index 28a63c5d36842..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AnalyticsCards } from '../../analytics'; - -import { TotalStats } from './total_stats'; - -describe('TotalStats', () => { - it('renders', () => { - setMockValues({ - totalQueries: 11, - documentCount: 22, - totalClicks: 33, - }); - const wrapper = shallow(); - expect(wrapper.find(AnalyticsCards)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx deleted file mode 100644 index 9a5fd0066db37..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EngineOverviewLogic } from '..'; -import { AnalyticsCards } from '../../analytics'; -import { TOTAL_QUERIES, TOTAL_DOCUMENTS, TOTAL_CLICKS } from '../../analytics/constants'; - -export const TotalStats: React.FC = () => { - const { totalQueries, documentCount, totalClicks } = useValues(EngineOverviewLogic); - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts deleted file mode 100644 index 379e1e99953af..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const OVERVIEW_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.title', - { defaultMessage: 'Overview' } -); - -export const VIEW_ANALYTICS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.analyticsLink', - { defaultMessage: 'View analytics' } -); - -export const VIEW_API_LOGS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.apiLogsLink', - { defaultMessage: 'View API logs' } -); - -export const LAST_7_DAYS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.chartDuration', - { defaultMessage: 'Last 7 days' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx deleted file mode 100644 index 943158a1935bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EmptyEngineOverview } from './engine_overview_empty'; -import { EngineOverviewMetrics } from './engine_overview_metrics'; - -import { EngineOverview } from '.'; - -describe('EngineOverview', () => { - const values = { - dataLoading: false, - myRole: {}, - hasNoDocuments: true, - isMetaEngine: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - describe('EmptyEngineOverview', () => { - it('renders when the engine has no documents & the user can add documents', () => { - const myRole = { canManageEngineDocuments: true, canViewEngineCredentials: true }; - setMockValues({ ...values, myRole }); - const wrapper = shallow(); - expect(wrapper.find(EmptyEngineOverview)).toHaveLength(1); - }); - }); - - describe('EngineOverviewMetrics', () => { - it('renders when the engine has documents', () => { - setMockValues({ ...values, hasNoDocuments: false }); - const wrapper = shallow(); - expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); - }); - - it('renders when the user does not have the ability to add documents', () => { - const myRole = { canManageEngineDocuments: false, canViewEngineCredentials: false }; - setMockValues({ ...values, myRole }); - const wrapper = shallow(); - expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); - }); - - it('always renders for meta engines', () => { - setMockValues({ ...values, isMetaEngine: true }); - const wrapper = shallow(); - expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx deleted file mode 100644 index f6c4031823ea5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { AppLogic } from '../../app_logic'; -import { EngineLogic } from '../engine'; - -import { EmptyEngineOverview } from './engine_overview_empty'; - -import { EngineOverviewMetrics } from './engine_overview_metrics'; - -export const EngineOverview: React.FC = () => { - const { - myRole: { canManageEngineDocuments, canViewEngineCredentials }, - } = useValues(AppLogic); - const { hasNoDocuments, isMetaEngine } = useValues(EngineLogic); - - const canAddDocuments = canManageEngineDocuments && canViewEngineCredentials; - const showEngineOverview = !hasNoDocuments || !canAddDocuments || isMetaEngine; - - return showEngineOverview ? : ; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx deleted file mode 100644 index 21f959a39e189..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 '../../__mocks__/engine_logic.mock'; -import { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../shared/doc_links'; -import { getPageTitle, getPageHeaderActions } from '../../../test_helpers'; - -import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; - -import { EmptyEngineOverview } from './engine_overview_empty'; - -describe('EmptyEngineOverview', () => { - let wrapper: ShallowWrapper; - const values = { - isElasticsearchEngine: false, - engine: { - elasticsearchIndexName: 'my-elasticsearch-index', - }, - }; - - beforeAll(() => { - setMockValues(values); - wrapper = shallow(); - }); - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders', () => { - expect(getPageTitle(wrapper)).toEqual('Engine setup'); - }); - - it('renders a documentation link', () => { - expect(getPageHeaderActions(wrapper).find(EuiButton).prop('href')).toEqual( - docLinks.appSearchGuide - ); - }); - - it('renders document creation components', () => { - expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); - expect(wrapper.find(DocumentCreationFlyout)).toHaveLength(1); - }); - - it('renders elasticsearch index empty state', () => { - setMockValues({ ...values, isElasticsearchEngine: true }); - wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="ElasticsearchIndexEmptyState"]')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx deleted file mode 100644 index e4bcf810d58f8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiButton, EuiEmptyPrompt, EuiImage, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../shared/doc_links'; -import { EuiButtonTo } from '../../../shared/react_router_helpers'; -import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; -import illustration from '../document_creation/illustration.svg'; - -import { EngineLogic, getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -export const EmptyEngineOverview: React.FC = () => { - const { - isElasticsearchEngine, - engine: { elasticsearchIndexName }, - } = useValues(EngineLogic); - - const elasticsearchEmptyState = ( - - } - title={ -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateTitle', { - defaultMessage: 'Add documents to your index', - })} -

    - } - layout="horizontal" - hasBorder - color="plain" - body={ - <> -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.elasticsearchEngine.helperText', { - defaultMessage: - "Your Elasticsearch index, {elasticsearchIndexName}, doesn't have any documents yet. Open Index Management in Kibana to make changes to your Elasticsearch indices.", - values: { elasticsearchIndexName }, - })} -

    - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.elasticsearchEngine.emptyStateButton', - { - defaultMessage: 'Manage indices', - } - )} - - - } - /> - ); - - return ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction', - { defaultMessage: 'View documentation' } - )} - , - ], - }} - data-test-subj="EngineOverview" - > - {isElasticsearchEngine ? ( - elasticsearchEmptyState - ) : ( - <> - - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts deleted file mode 100644 index 69e7cf3053d30..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../__mocks__/kea_logic'; - -jest.mock('../engine', () => ({ - EngineLogic: { values: { engineName: 'some-engine' } }, -})); - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { EngineOverviewLogic } from '.'; - -describe('EngineOverviewLogic', () => { - const { mount } = new LogicMounter(EngineOverviewLogic); - const { http } = mockHttpValues; - - const mockEngineMetrics = { - documentCount: 10, - startDate: '1970-01-30', - operationsPerDay: [0, 0, 0, 0, 0, 0, 0], - queriesPerDay: [0, 0, 0, 0, 0, 25, 50], - totalClicks: 50, - totalQueries: 75, - }; - - const DEFAULT_VALUES = { - dataLoading: true, - documentCount: 0, - startDate: '', - operationsPerDay: [], - queriesPerDay: [], - totalClicks: 0, - totalQueries: 0, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(EngineOverviewLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onOverviewMetricsLoad', () => { - it('should set all received data as top-level values and set dataLoading to false', () => { - mount(); - EngineOverviewLogic.actions.onOverviewMetricsLoad(mockEngineMetrics); - - expect(EngineOverviewLogic.values).toEqual({ - ...DEFAULT_VALUES, - ...mockEngineMetrics, - dataLoading: false, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadOverviewMetrics', () => { - it('fetches data and calls onOverviewMetricsLoad', async () => { - mount(); - jest.spyOn(EngineOverviewLogic.actions, 'onOverviewMetricsLoad'); - http.get.mockReturnValueOnce(Promise.resolve(mockEngineMetrics)); - - EngineOverviewLogic.actions.loadOverviewMetrics(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/overview'); - expect(EngineOverviewLogic.actions.onOverviewMetricsLoad).toHaveBeenCalledWith( - mockEngineMetrics - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - EngineOverviewLogic.actions.loadOverviewMetrics(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts deleted file mode 100644 index 3872a6af199f1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { EngineLogic } from '../engine'; - -interface EngineOverviewApiData { - documentCount: number; - startDate: string; - operationsPerDay: number[]; - queriesPerDay: number[]; - totalClicks: number; - totalQueries: number; -} -interface EngineOverviewValues extends EngineOverviewApiData { - dataLoading: boolean; -} - -interface EngineOverviewActions { - loadOverviewMetrics(): void; - onOverviewMetricsLoad(response: EngineOverviewApiData): EngineOverviewApiData; -} - -export const EngineOverviewLogic = kea>({ - path: ['enterprise_search', 'app_search', 'engine_overview_logic'], - actions: () => ({ - loadOverviewMetrics: true, - onOverviewMetricsLoad: (engineMetrics) => engineMetrics, - }), - reducers: () => ({ - dataLoading: [ - true, - { - onOverviewMetricsLoad: () => false, - }, - ], - startDate: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - onOverviewMetricsLoad: (_, { startDate }) => startDate, - }, - ], - queriesPerDay: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onOverviewMetricsLoad: (_, { queriesPerDay }) => queriesPerDay, - }, - ], - operationsPerDay: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onOverviewMetricsLoad: (_, { operationsPerDay }) => operationsPerDay, - }, - ], - totalQueries: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onOverviewMetricsLoad: (_, { totalQueries }) => totalQueries, - }, - ], - totalClicks: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onOverviewMetricsLoad: (_, { totalClicks }) => totalClicks, - }, - ], - documentCount: [ - 0, - { - // @ts-expect-error upgrade typescript v5.1.6 - onOverviewMetricsLoad: (_, { documentCount }) => documentCount, - }, - ], - }), - listeners: ({ actions }) => ({ - loadOverviewMetrics: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/overview` - ); - actions.onOverviewMetricsLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx deleted file mode 100644 index c80e5c2208c31..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { getPageTitle } from '../../../test_helpers'; - -import { TotalStats, TotalCharts, RecentApiLogs } from './components'; -import { SuggestedCurationsCallout } from './components/suggested_curations_callout'; -import { EngineOverviewMetrics } from './engine_overview_metrics'; - -describe('EngineOverviewMetrics', () => { - const values = { - dataLoading: false, - }; - const actions = { - loadOverviewMetrics: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(getPageTitle(wrapper)).toEqual('Engine overview'); - expect(wrapper.find(SuggestedCurationsCallout)).toHaveLength(1); - expect(wrapper.find(TotalStats)).toHaveLength(1); - expect(wrapper.find(TotalCharts)).toHaveLength(1); - expect(wrapper.find(RecentApiLogs)).toHaveLength(1); - }); - - it('initializes data on mount', () => { - shallow(); - expect(actions.loadOverviewMetrics).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx deleted file mode 100644 index 66292e0d3618d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { TotalStats, TotalCharts, RecentApiLogs } from './components'; - -import { SuggestedCurationsCallout } from './components/suggested_curations_callout'; - -import { EngineOverviewLogic } from '.'; - -export const EngineOverviewMetrics: React.FC = () => { - const { loadOverviewMetrics } = useActions(EngineOverviewLogic); - const { dataLoading } = useValues(EngineOverviewLogic); - - useEffect(() => { - loadOverviewMetrics(); - }, []); - - return ( - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts deleted file mode 100644 index 2184d9207c4ec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { EngineOverviewLogic } from './engine_overview_logic'; -export { EngineOverview } from './engine_overview'; -export { OVERVIEW_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.scss deleted file mode 100644 index 11a008a3cc51f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.scss +++ /dev/null @@ -1,9 +0,0 @@ -.auditLogsModal { - width: 75vw; -} - -@media (max-width: 1200px) { - .auditLogsModal { - width: 100vw; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.test.tsx deleted file mode 100644 index f6687e431e983..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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, setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiText, EuiModal } from '@elastic/eui'; - -import { EntSearchLogStream } from '../../../../../shared/log_stream'; - -import { AuditLogsModal } from './audit_logs_modal'; - -import { AuditLogsModalLogic } from './audit_logs_modal_logic'; - -describe('AuditLogsModal', () => { - const { mount } = new LogicMounter(AuditLogsModalLogic); - beforeEach(() => { - jest.clearAllMocks(); - mount({ isModalVisible: true }); - }); - - it('renders nothing by default', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('renders the modal when modal visible', () => { - const testEngineName = 'test-engine-123'; - const mockClose = jest.fn(); - setMockValues({ - isModalVisible: true, - engineName: testEngineName, - }); - setMockActions({ - hideModal: mockClose, - }); - - const wrapper = shallow(); - expect(wrapper.find(EntSearchLogStream).prop('query')).toBe( - `event.kind: event and event.action: audit and enterprisesearch.data_repository.name: ${testEngineName}` - ); - expect(wrapper.find(EuiText).children().text()).toBe('Showing events from last 24 hours'); - expect(wrapper.find(EuiModal).prop('onClose')).toBe(mockClose); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx deleted file mode 100644 index 595d881be7b9e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiButton, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID } from '../../../../../../../common/constants'; -import { EntSearchLogStream } from '../../../../../shared/log_stream'; - -import { AuditLogsModalLogic } from './audit_logs_modal_logic'; - -import './audit_logs_modal.scss'; - -export const AuditLogsModal: React.FC = () => { - const auditLogsModalLogic = AuditLogsModalLogic(); - const { isModalVisible, engineName } = useValues(auditLogsModalLogic); - const { hideModal } = useActions(auditLogsModalLogic); - - const filters = [ - 'event.kind: event', - 'event.action: audit', - `enterprisesearch.data_repository.name: ${engineName}`, - ].join(' and '); - - return !isModalVisible ? null : ( - - - {engineName} - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engines.auditLogsModal.eventTip', { - defaultMessage: 'Showing events from last 24 hours', - })} - - - - - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engines.auditLogsModal.closeButton', { - defaultMessage: 'Close', - })} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.test.ts deleted file mode 100644 index f869dd145087d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { AuditLogsModalLogic } from './audit_logs_modal_logic'; - -describe('AuditLogsModalLogic', () => { - const { mount } = new LogicMounter(AuditLogsModalLogic); - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has excepted default values', () => { - expect(AuditLogsModalLogic.values).toEqual({ - isModalVisible: false, - engineName: '', - }); - }); - - describe('actions', () => { - describe('hideModal', () => { - it('hides the modal', () => { - mount({ - isModalVisible: true, - engineName: 'test_engine', - }); - - AuditLogsModalLogic.actions.hideModal(); - expect(AuditLogsModalLogic.values).toEqual({ - isModalVisible: false, - engineName: '', - }); - }); - }); - - describe('showModal', () => { - it('show the modal with correct engine name', () => { - AuditLogsModalLogic.actions.showModal('test-engine-123'); - expect(AuditLogsModalLogic.values).toEqual({ - isModalVisible: true, - engineName: 'test-engine-123', - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.ts deleted file mode 100644 index 2f03228ff2f65..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 } from 'kea'; - -export const AuditLogsModalLogic = kea({ - path: ['enterprise_search', 'app_search', 'engines_overview', 'audit_logs_modal'], - actions: () => ({ - hideModal: true, - showModal: (engineName: string) => ({ engineName }), - }), - reducers: () => ({ - isModalVisible: [ - false, - { - showModal: () => true, - hideModal: () => false, - }, - ], - engineName: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - showModal: (_, { engineName }) => engineName, - hideModal: () => '', - }, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx deleted file mode 100644 index ceee401b637b1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyMetaEnginesState } from '.'; - -describe('EmptyMetaEnginesState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h3').text()).toEqual('Create your first meta engine'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchMetaEngines) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx deleted file mode 100644 index 0824997ba8896..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyMetaEnginesState: React.FC = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptTitle', { - defaultMessage: 'Create your first meta engine', - })} - - } - titleSize="s" - body={ -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptDescription', - { - defaultMessage: - 'Meta engines allow you to combine multiple engines into one searchable engine.', - } - )} -

    - } - actions={ - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptButtonLabel', - { defaultMessage: 'Learn more about meta engines' } - )} - - } - /> -); 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 deleted file mode 100644 index 8fcb57a510a2f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 { setMockValues, mockTelemetryActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiEmptyPrompt } from '@elastic/eui'; - -import { SampleEngineCreationCta } from '../../sample_engine_creation_cta'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - describe('when the user can manage/create engines', () => { - let wrapper: ShallowWrapper; - let prompt: ShallowWrapper; - - beforeAll(() => { - setMockValues({ myRole: { canManageEngines: true } }); - wrapper = shallow(); - prompt = wrapper.find(EuiEmptyPrompt).dive(); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('renders a prompt to create an engine', () => { - expect(wrapper.find('[data-test-subj="AdminEmptyEnginesPrompt"]')).toHaveLength(1); - }); - - it('contains a sample engine CTA', () => { - expect(prompt.find(SampleEngineCreationCta)).toHaveLength(1); - }); - - describe('create engine button', () => { - let button: ShallowWrapper; - - beforeAll(() => { - button = prompt.find('[data-test-subj="EmptyStateCreateFirstEngineCta"]'); - }); - - it('sends telemetry on create first engine click', () => { - button.simulate('click'); - expect(mockTelemetryActions.sendAppSearchTelemetry).toHaveBeenCalled(); - }); - - it('sends a user to engine creation', () => { - expect(button.prop('to')).toEqual('/engines/new'); - }); - }); - }); - - describe('when the user cannot manage/create engines', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - setMockValues({ myRole: { canManageEngines: false } }); - wrapper = shallow(); - }); - - afterAll(() => { - jest.clearAllMocks(); - }); - - it('renders a prompt to contact the App Search admin', () => { - expect(wrapper.find('[data-test-subj="NonAdminEmptyEnginesPrompt"]')).toHaveLength(1); - }); - }); -}); 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 deleted file mode 100644 index df17d22d387d9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EuiButtonTo } from '../../../../shared/react_router_helpers'; -import { TelemetryLogic } from '../../../../shared/telemetry'; -import { AppLogic } from '../../../app_logic'; -import { EngineIcon } from '../../../icons'; -import { ENGINE_CREATION_PATH } from '../../../routes'; - -import { SampleEngineCreationCta } from '../../sample_engine_creation_cta/sample_engine_creation_cta'; - -export const EmptyState: React.FC = () => { - const { - myRole: { canManageEngines }, - } = 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.', - })} -

    - } - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/index.ts deleted file mode 100644 index 63235f8a992f0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { EmptyState } from './empty_state'; -export { EmptyMetaEnginesState } from './empty_meta_engines_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/__mocks__/engines_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/__mocks__/engines_logic.mock.ts deleted file mode 100644 index bc954790debba..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/__mocks__/engines_logic.mock.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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. - */ - -jest.mock('../../..', () => ({ - EnginesLogic: { actions: { deleteEngine: jest.fn() } }, -})); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.test.tsx deleted file mode 100644 index 1ddf6e40ce1ff..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { mockKibanaValues, mockTelemetryActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; - -import { navigateToEngine, renderEngineLink } from './engine_link_helpers'; - -describe('navigateToEngine', () => { - const { navigateToUrl } = mockKibanaValues; - const { sendAppSearchTelemetry } = mockTelemetryActions; - - it('sends the user to the engine page and triggers a telemetry event', () => { - navigateToEngine('engine-a'); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/engine-a'); - expect(sendAppSearchTelemetry).toHaveBeenCalledWith({ - action: 'clicked', - metric: 'engine_table_link', - }); - }); -}); - -describe('renderEngineLink', () => { - const { sendAppSearchTelemetry } = mockTelemetryActions; - - it('renders a link to the engine with telemetry', () => { - const wrapper = shallow(
    {renderEngineLink('engine-b')}
    ); - const link = wrapper.find(EuiLinkTo); - - expect(link.prop('to')).toEqual('/engines/engine-b'); - - link.simulate('click'); - expect(sendAppSearchTelemetry).toHaveBeenCalledWith({ - action: 'clicked', - metric: 'engine_table_link', - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx deleted file mode 100644 index 229e0def4700e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { EuiLink } from '@elastic/eui'; - -import { KibanaLogic } from '../../../../../shared/kibana'; -import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; -import { TelemetryLogic } from '../../../../../shared/telemetry'; -import { ENGINE_PATH } from '../../../../routes'; -import { generateEncodedPath } from '../../../../utils/encode_path_params'; -import { FormattedDateTime } from '../../../../utils/formatted_date_time'; - -const sendEngineTableLinkClickTelemetry = () => { - TelemetryLogic.actions.sendAppSearchTelemetry({ - action: 'clicked', - metric: 'engine_table_link', - }); -}; - -export const navigateToEngine = (engineName: string) => { - sendEngineTableLinkClickTelemetry(); - KibanaLogic.values.navigateToUrl(generateEncodedPath(ENGINE_PATH, { engineName })); -}; - -export const renderEngineLink = (engineName: string) => ( - - {engineName} - -); - -export const renderLastChangeLink = (dateString: string, onClick = () => {}) => ( - - {!dateString ? '-' : } - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.test.tsx deleted file mode 100644 index 11cdd780bfc86..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/enterprise_search_url.mock'; -import './__mocks__/engines_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBasicTable } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { EngineDetails } from '../../../engine/types'; - -import { EnginesTable } from './engines_table'; - -import { runSharedColumnsTests, runSharedPropsTests } from './test_helpers'; - -describe('EnginesTable', () => { - const data = [ - { - name: 'test-engine', - created_at: 'Fri, 1 Jan 1970 12:00:00 +0000', - language: 'English', - isMeta: false, - document_count: 99999, - field_count: 10, - } as EngineDetails, - ]; - const props = { - items: data, - loading: false, - pagination: { - pageIndex: 0, - pageSize: 10, - totalItemCount: 1, - showPerPageOptions: false, - }, - onChange: () => {}, - }; - setMockValues({ myRole: { canManageEngines: false } }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiBasicTable)).toHaveLength(1); - }); - - describe('columns', () => { - const wrapper = shallow(); - const tableContent = mountWithIntl() - .find(EuiBasicTable) - .text(); - runSharedColumnsTests(wrapper, tableContent); - }); - - describe('language column', () => { - it('renders language when set', () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find(EuiBasicTable).text()).toContain('German'); - }); - - it('renders the language as Universal if no language is set', () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find(EuiBasicTable).text()).toContain('Universal'); - }); - }); - - describe('passed props', () => { - const wrapper = shallow(); - runSharedPropsTests(wrapper); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx deleted file mode 100644 index 5e6ece1003e7f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { EuiBasicTable, EuiBasicTableColumn, EuiTableFieldDataColumnType } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { AppLogic } from '../../../../app_logic'; -import { UNIVERSAL_LANGUAGE } from '../../../../constants'; -import { EngineDetails } from '../../../engine/types'; - -import { AuditLogsModalLogic } from '../audit_logs_modal/audit_logs_modal_logic'; - -import { renderEngineLink, renderLastChangeLink } from './engine_link_helpers'; -import { - ACTIONS_COLUMN, - CREATED_AT_COLUMN, - LAST_UPDATED_COLUMN, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - NAME_COLUMN, -} from './shared_columns'; -import { EnginesTableProps } from './types'; - -const LANGUAGE_COLUMN: EuiTableFieldDataColumnType = { - field: 'language', - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.language', { - defaultMessage: 'Language', - }), - dataType: 'string', - render: (language: string) => language || UNIVERSAL_LANGUAGE, -}; - -export const EnginesTable: React.FC = ({ - items, - loading, - noItemsMessage, - pagination, - onChange, -}) => { - const { - myRole: { canManageEngines }, - } = useValues(AppLogic); - - const { showModal: showAuditLogModal } = useActions(AuditLogsModalLogic); - - const columns: Array> = [ - { - ...NAME_COLUMN, - render: (name: string) => renderEngineLink(name), - }, - CREATED_AT_COLUMN, - { - ...LAST_UPDATED_COLUMN, - render: (dateString: string, engineDetails) => { - return renderLastChangeLink(dateString, () => { - showAuditLogModal(engineDetails.name); - }); - }, - }, - LANGUAGE_COLUMN, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - ]; - - if (canManageEngines) { - columns.push(ACTIONS_COLUMN); - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.test.tsx deleted file mode 100644 index b76f007a3a5b4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/enterprise_search_url.mock'; -import './__mocks__/engines_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBasicTable } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { EngineDetails } from '../../../engine/types'; - -import { MetaEnginesTable } from './meta_engines_table'; -import { MetaEnginesTableExpandedRow } from './meta_engines_table_expanded_row'; -import { MetaEnginesTableNameColumnContent } from './meta_engines_table_name_column_content'; - -import { runSharedColumnsTests, runSharedPropsTests } from './test_helpers'; - -describe('MetaEnginesTable', () => { - const data = [ - { - name: 'test-engine', - created_at: 'Fri, 1 Jan 1970 12:00:00 +0000', - isMeta: true, - document_count: 99999, - field_count: 10, - includedEngines: [{ name: 'source-engine-1' }, { name: 'source-engine-2' }], - } as EngineDetails, - ]; - const props = { - items: data, - loading: false, - pagination: { - pageIndex: 0, - pageSize: 10, - totalItemCount: 1, - showPerPageOptions: false, - }, - onChange: () => {}, - }; - - const DEFAULT_VALUES = { - myRole: { - canManageMetaEngines: false, - }, - expandedSourceEngines: {}, - hideRow: jest.fn(), - fetchOrDisplayRow: jest.fn(), - }; - setMockValues(DEFAULT_VALUES); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiBasicTable)).toHaveLength(1); - }); - - describe('columns', () => { - const wrapper = shallow(); - const tableContent = mountWithIntl() - .find(EuiBasicTable) - .text(); - runSharedColumnsTests(wrapper, tableContent, DEFAULT_VALUES); - }); - - describe('passed props', () => { - const wrapper = shallow(); - runSharedPropsTests(wrapper); - }); - - describe('expanded source engines', () => { - it('is hidden by default', () => { - const wrapper = shallow(); - const table = wrapper.find(EuiBasicTable).dive(); - // @ts-expect-error upgrade typescript v5.1.6 - const tableBody = table.find('RenderWithEuiTheme').renderProp('children')(); - - expect(tableBody.find(MetaEnginesTableNameColumnContent)).toHaveLength(1); - expect(tableBody.find(MetaEnginesTableExpandedRow)).toHaveLength(0); - }); - - it('is visible when the row has been expanded', () => { - setMockValues({ - ...DEFAULT_VALUES, - expandedSourceEngines: { 'test-engine': true }, - }); - const wrapper = shallow(); - const table = wrapper.find(EuiBasicTable).dive(); - // @ts-expect-error upgrade typescript v5.1.6 - const tableBody = table.find('RenderWithEuiTheme').renderProp('children')(); - - expect(tableBody.find(MetaEnginesTableExpandedRow)).toHaveLength(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx deleted file mode 100644 index 24eb8cc8a6b81..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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, { ReactNode, useMemo } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; - -import { AppLogic } from '../../../../app_logic'; -import { EngineDetails } from '../../../engine/types'; - -import { AuditLogsModalLogic } from '../audit_logs_modal/audit_logs_modal_logic'; - -import { renderLastChangeLink } from './engine_link_helpers'; -import { MetaEnginesTableExpandedRow } from './meta_engines_table_expanded_row'; -import { MetaEnginesTableLogic } from './meta_engines_table_logic'; -import { MetaEnginesTableNameColumnContent } from './meta_engines_table_name_column_content'; -import { - ACTIONS_COLUMN, - BLANK_COLUMN, - CREATED_AT_COLUMN, - LAST_UPDATED_COLUMN, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - NAME_COLUMN, -} from './shared_columns'; -import { EnginesTableProps } from './types'; -import { getConflictingEnginesSet } from './utils'; - -interface IItemIdToExpandedRowMap { - [id: string]: ReactNode; -} - -export interface ConflictingEnginesSets { - [key: string]: Set; -} - -export const MetaEnginesTable: React.FC = ({ - items, - loading, - noItemsMessage, - pagination, - onChange, -}) => { - const { expandedSourceEngines } = useValues(MetaEnginesTableLogic); - const { hideRow, fetchOrDisplayRow } = useActions(MetaEnginesTableLogic); - const { - myRole: { canManageMetaEngines }, - } = useValues(AppLogic); - - const { showModal: showAuditLogModal } = useActions(AuditLogsModalLogic); - - const conflictingEnginesSets: ConflictingEnginesSets = useMemo( - () => - items.reduce((accumulator, metaEngine) => { - return { - ...accumulator, - [metaEngine.name]: getConflictingEnginesSet(metaEngine), - }; - }, {}), - [items] - ); - - const itemIdToExpandedRowMap: IItemIdToExpandedRowMap = useMemo( - () => - Object.keys(expandedSourceEngines).reduce((accumulator, engineName) => { - return { - ...accumulator, - [engineName]: ( - - ), - }; - }, {}), - [expandedSourceEngines, conflictingEnginesSets] - ); - - const columns: Array> = [ - { - ...NAME_COLUMN, - render: (_, item: EngineDetails) => ( - - ), - }, - CREATED_AT_COLUMN, - { - ...LAST_UPDATED_COLUMN, - render: (dateString: string, engineDetails) => { - return renderLastChangeLink(dateString, () => { - showAuditLogModal(engineDetails.name); - }); - }, - }, - BLANK_COLUMN, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - ]; - - if (canManageMetaEngines) { - columns.push(ACTIONS_COLUMN); - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.scss deleted file mode 100644 index e6f627458f43e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.scss +++ /dev/null @@ -1,21 +0,0 @@ -.metaEnginesSourceEnginesTable { - margin: (-$euiSizeS) (-$euiSizeS) $euiSizeS (-$euiSizeS); - - thead { - display: none; - } - - @include euiBreakpoint('l', 'xl') { - .euiTableRowCell { - border-top: none; - } - - .euiTitle { - display: none; - } - } - - .euiTableHeaderMobile { - display: none - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.test.tsx deleted file mode 100644 index b5f7e7521e5e8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 { EuiBasicTable, EuiHealth } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../../test_helpers'; - -import { EngineDetails } from '../../../engine/types'; - -import { MetaEnginesTableExpandedRow } from './meta_engines_table_expanded_row'; - -const SOURCE_ENGINES = [ - { - name: 'source-engine-1', - created_at: 'Fri, 1 Jan 1970 12:00:00 +0000', - language: 'English', - isMeta: true, - document_count: 99999, - field_count: 10, - }, - { - name: 'source-engine-2', - created_at: 'Fri, 1 Jan 1970 12:00:00 +0000', - language: 'English', - isMeta: true, - document_count: 55555, - field_count: 7, - }, -] as EngineDetails[]; - -describe('MetaEnginesTableExpandedRow', () => { - it('contains relevant source engine information', () => { - const wrapper = mountWithIntl( - - ); - const table = wrapper.find(EuiBasicTable); - - expect(table).toHaveLength(1); - - const tableContent = table.text(); - expect(tableContent).toContain('source-engine-1'); - expect(tableContent).toContain('99,999'); - expect(tableContent).toContain('10'); - - expect(tableContent).toContain('source-engine-2'); - expect(tableContent).toContain('55,555'); - expect(tableContent).toContain('7'); - }); - - it('indicates when a meta-engine has conflicts', () => { - const wrapper = shallow( - - ); - const table = wrapper.find(EuiBasicTable).dive(); - // @ts-expect-error upgrade typescript v5.1.6 - const tableBody = table.find('RenderWithEuiTheme').renderProp('children')(); - - expect(tableBody.find(EuiHealth)).toHaveLength(2); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.tsx deleted file mode 100644 index 0f974581ca73c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_expanded_row.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { EuiBasicTable, EuiHealth, EuiTitle } from '@elastic/eui'; - -import { EngineDetails } from '../../../engine/types'; -import { SOURCE_ENGINES_TITLE } from '../../constants'; - -import { - BLANK_COLUMN, - CREATED_AT_COLUMN, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - NAME_COLUMN, -} from './shared_columns'; - -import './meta_engines_table_expanded_row.scss'; - -interface MetaEnginesTableExpandedRowProps { - sourceEngines: EngineDetails[]; - conflictingEngines: Set; -} - -export const MetaEnginesTableExpandedRow: React.FC = ({ - sourceEngines, - conflictingEngines, -}) => ( -
    - -

    {SOURCE_ENGINES_TITLE}

    -
    - ( - <> - {conflictingEngines.has(engineDetails.name) ? ( - {engineDetails.field_count} - ) : ( - engineDetails.field_count - )} - - ), - }, - BLANK_COLUMN, - ]} - /> -
    -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_logic.test.ts deleted file mode 100644 index ae04547aae8f6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_logic.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 { mockRecursivelyFetchEngines } from '../../../../__mocks__/recursively_fetch_engines.mock'; - -import { EngineDetails } from '../../../engine/types'; - -import { MetaEnginesTableLogic } from './meta_engines_table_logic'; - -describe('MetaEnginesTableLogic', () => { - const { mount } = new LogicMounter(MetaEnginesTableLogic); - - const DEFAULT_VALUES = { - expandedRows: {}, - sourceEngines: {}, - expandedSourceEngines: {}, - }; - - const SOURCE_ENGINES = [ - { - name: 'source-engine-1', - }, - { - name: 'source-engine-2', - }, - ] as EngineDetails[]; - - const META_ENGINES = [ - { - name: 'test-engine-1', - includedEngines: SOURCE_ENGINES, - }, - { - name: 'test-engine-2', - includedEngines: SOURCE_ENGINES, - }, - ] as EngineDetails[]; - - const DEFAULT_PROPS = { - metaEngines: [...SOURCE_ENGINES, ...META_ENGINES] as EngineDetails[], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount({}, DEFAULT_PROPS); - expect(MetaEnginesTableLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('reducers', () => { - describe('expandedRows', () => { - it('displayRow adds an expanded row entry for provided itemId', () => { - mount(DEFAULT_VALUES, DEFAULT_PROPS); - MetaEnginesTableLogic.actions.displayRow('source-engine-1'); - - expect(MetaEnginesTableLogic.values.expandedRows).toEqual({ - 'source-engine-1': true, - }); - }); - - it('hideRow removes any expanded row entry for provided itemId', () => { - mount({ ...DEFAULT_VALUES, expandedRows: { 'source-engine-1': true } }, DEFAULT_PROPS); - - MetaEnginesTableLogic.actions.hideRow('source-engine-1'); - - expect(MetaEnginesTableLogic.values.expandedRows).toEqual({}); - }); - }); - - it('sourceEngines is updated by addSourceEngines', () => { - mount({ - ...DEFAULT_VALUES, - sourceEngines: { - 'test-engine-1': [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[], - }, - }); - - MetaEnginesTableLogic.actions.addSourceEngines({ - 'test-engine-2': [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[], - }); - - expect(MetaEnginesTableLogic.values.sourceEngines).toEqual({ - 'test-engine-1': [{ name: 'source-engine-1' }, { name: 'source-engine-2' }], - 'test-engine-2': [{ name: 'source-engine-1' }, { name: 'source-engine-2' }], - }); - }); - }); - - describe('listeners', () => { - describe('fetchOrDisplayRow', () => { - it('calls displayRow when it already has data for the itemId', () => { - mount({ - ...DEFAULT_VALUES, - sourceEngines: { - 'test-engine-1': [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[], - }, - }); - jest.spyOn(MetaEnginesTableLogic.actions, 'displayRow'); - - MetaEnginesTableLogic.actions.fetchOrDisplayRow('test-engine-1'); - - expect(MetaEnginesTableLogic.actions.displayRow).toHaveBeenCalled(); - }); - - it('calls fetchSourceEngines when it needs to fetch data for the itemId', () => { - mount(); - jest.spyOn(MetaEnginesTableLogic.actions, 'fetchSourceEngines'); - - MetaEnginesTableLogic.actions.fetchOrDisplayRow('test-engine-1'); - - expect(MetaEnginesTableLogic.actions.fetchSourceEngines).toHaveBeenCalled(); - }); - }); - - describe('fetchSourceEngines', () => { - it('calls addSourceEngines and displayRow when it has retrieved all pages', () => { - mount(); - jest.spyOn(MetaEnginesTableLogic.actions, 'displayRow'); - jest.spyOn(MetaEnginesTableLogic.actions, 'addSourceEngines'); - - MetaEnginesTableLogic.actions.fetchSourceEngines('test-engine-1'); - - expect(mockRecursivelyFetchEngines).toHaveBeenCalledWith( - expect.objectContaining({ - endpoint: '/internal/app_search/engines/test-engine-1/source_engines', - }) - ); - expect(MetaEnginesTableLogic.actions.addSourceEngines).toHaveBeenCalledWith({ - 'test-engine-1': [{ name: 'source-engine-1' }, { name: 'source-engine-2' }], - }); - expect(MetaEnginesTableLogic.actions.displayRow).toHaveBeenCalledWith('test-engine-1'); - }); - }); - }); - - describe('selectors', () => { - it('expandedSourceEngines includes all source engines that have been expanded ', () => { - mount({ - ...DEFAULT_VALUES, - sourceEngines: { - 'test-engine-1': [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[], - 'test-engine-2': [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[], - }, - expandedRows: { - 'test-engine-1': true, - }, - }); - - expect(MetaEnginesTableLogic.values.expandedSourceEngines).toEqual({ - 'test-engine-1': [{ name: 'source-engine-1' }, { name: 'source-engine-2' }], - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_logic.ts deleted file mode 100644 index 5bb1a09e94d4b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_logic.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 { recursivelyFetchEngines } from '../../../../utils/recursively_fetch_engines'; -import { EngineDetails } from '../../../engine/types'; - -interface MetaEnginesTableValues { - expandedRows: { [id: string]: boolean }; - sourceEngines: { [id: string]: EngineDetails[] }; - expandedSourceEngines: { [id: string]: EngineDetails[] }; -} - -interface MetaEnginesTableActions { - addSourceEngines(sourceEngines: MetaEnginesTableValues['sourceEngines']): { - sourceEngines: MetaEnginesTableValues['sourceEngines']; - }; - displayRow(itemId: string): { itemId: string }; - fetchOrDisplayRow(itemId: string): { itemId: string }; - fetchSourceEngines(engineName: string): { engineName: string }; - hideRow(itemId: string): { itemId: string }; -} - -export const MetaEnginesTableLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'meta_engines_table_logic'], - actions: () => ({ - addSourceEngines: (sourceEngines) => ({ sourceEngines }), - displayRow: (itemId) => ({ itemId }), - hideRow: (itemId) => ({ itemId }), - fetchOrDisplayRow: (itemId) => ({ itemId }), - fetchSourceEngines: (engineName) => ({ engineName }), - }), - reducers: () => ({ - expandedRows: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - displayRow: (expandedRows, { itemId }) => ({ - ...expandedRows, - [itemId]: true, - }), - // @ts-expect-error upgrade typescript v5.1.6 - hideRow: (expandedRows, { itemId }) => { - const newRows = { ...expandedRows }; - delete newRows[itemId]; - return newRows; - }, - }, - ], - sourceEngines: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - addSourceEngines: (currentSourceEngines, { sourceEngines: newSourceEngines }) => ({ - ...currentSourceEngines, - ...newSourceEngines, - }), - }, - ], - }), - selectors: { - expandedSourceEngines: [ - (selectors) => [selectors.sourceEngines, selectors.expandedRows], - (sourceEngines: MetaEnginesTableValues['sourceEngines'], expandedRows: string[]) => { - return Object.keys(expandedRows).reduce((expandedRowMap, engineName) => { - expandedRowMap[engineName] = sourceEngines[engineName]; - return expandedRowMap; - }, {} as MetaEnginesTableValues['sourceEngines']); - }, - ], - }, - listeners: ({ actions, values }) => ({ - fetchOrDisplayRow: ({ itemId }) => { - const sourceEngines = values.sourceEngines; - if (sourceEngines[itemId]) { - actions.displayRow(itemId); - } else { - actions.fetchSourceEngines(itemId); - } - }, - fetchSourceEngines: ({ engineName }) => { - recursivelyFetchEngines({ - endpoint: `/internal/app_search/engines/${engineName}/source_engines`, - onComplete: (sourceEngines) => { - actions.addSourceEngines({ [engineName]: sourceEngines }); - actions.displayRow(engineName); - }, - }); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_name_column_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_name_column_content.test.tsx deleted file mode 100644 index bad55b2542c70..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_name_column_content.test.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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 { EuiHealth } from '@elastic/eui'; - -import { SchemaConflictFieldTypes, SchemaConflicts } from '../../../../../shared/schema/types'; -import { EngineDetails } from '../../../engine/types'; - -import { MetaEnginesTableNameColumnContent } from './meta_engines_table_name_column_content'; - -describe('MetaEnginesTableNameColumnContent', () => { - it('includes the name of the engine', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find('[data-test-subj="EngineName"]')).toHaveLength(1); - }); - - describe('toggle button', () => { - it('displays expanded row when the row is currently hidden', () => { - const showRow = jest.fn(); - - const wrapper = shallow( - - ); - wrapper.find('[data-test-subj="ExpandRowButton"]').at(0).simulate('click'); - - expect(showRow).toHaveBeenCalled(); - }); - - it('hides expanded row when the row is currently visible', () => { - const hideRow = jest.fn(); - - const wrapper = shallow( - - ); - wrapper.find('[data-test-subj="ExpandRowButton"]').at(0).simulate('click'); - - expect(hideRow).toHaveBeenCalled(); - }); - }); - - describe('engine count', () => { - it('is included and labelled', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find('[data-test-subj="SourceEnginesCount"]')).toHaveLength(1); - }); - }); - - it('indicates the precense of field-type conflicts', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(EuiHealth)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_name_column_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_name_column_content.tsx deleted file mode 100644 index e05246ab4d92c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table_name_column_content.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { EuiFlexGroup, EuiIcon, EuiHealth, EuiFlexItem } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { EngineDetails } from '../../../engine/types'; - -import { renderEngineLink } from './engine_link_helpers'; - -interface MetaEnginesTableNameContentProps { - isExpanded: boolean; - item: EngineDetails; - hideRow: (name: string) => void; - showRow: (name: string) => void; -} - -export const MetaEnginesTableNameColumnContent: React.FC = ({ - item: { name, schemaConflicts, engine_count: engineCount }, - isExpanded, - hideRow, - showRow, -}) => ( - - {renderEngineLink(name)} - - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx deleted file mode 100644 index 1e76ab2b749fe..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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 { - EuiTableFieldDataColumnType, - EuiTableComputedColumnType, - EuiTableActionsColumnType, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedNumber } from '@kbn/i18n-react'; - -import { EnginesLogic } from '../..'; -import { MANAGE_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../../shared/constants'; -import { FormattedDateTime } from '../../../../utils/formatted_date_time'; -import { EngineDetails } from '../../../engine/types'; - -import { navigateToEngine } from './engine_link_helpers'; - -export const BLANK_COLUMN: EuiTableComputedColumnType = { - render: () => <>, - 'aria-hidden': true, -}; - -export const NAME_COLUMN: EuiTableFieldDataColumnType = { - field: 'name', - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', { - defaultMessage: 'Name', - }), - width: '30%', - truncateText: true, - mobileOptions: { - header: true, - enlarge: true, - width: '100%', - }, -}; - -export const CREATED_AT_COLUMN: EuiTableFieldDataColumnType = { - field: 'created_at', - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt', { - defaultMessage: 'Created at', - }), - dataType: 'string', - render: (dateString: string) => , -}; - -export const LAST_UPDATED_COLUMN: EuiTableFieldDataColumnType = { - field: 'updated_at', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.lastUpdated', - { - defaultMessage: 'Last updated', - } - ), - dataType: 'string', -}; - -export const DOCUMENT_COUNT_COLUMN: EuiTableFieldDataColumnType = { - field: 'document_count', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount', - { - defaultMessage: 'Document count', - } - ), - dataType: 'number', - render: (number: number) => , - truncateText: true, -}; - -export const FIELD_COUNT_COLUMN: EuiTableFieldDataColumnType = { - field: 'field_count', - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount', { - defaultMessage: 'Field count', - }), - dataType: 'number', - render: (number: number) => , - truncateText: true, -}; - -export const ACTIONS_COLUMN: EuiTableActionsColumnType = { - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions', { - defaultMessage: 'Actions', - }), - actions: [ - { - name: MANAGE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage.buttonDescription', - { - defaultMessage: 'Manage this engine', - } - ), - type: 'icon', - icon: 'eye', - onClick: (engineDetails) => navigateToEngine(engineDetails.name), - }, - { - name: DELETE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.buttonDescription', - { - defaultMessage: 'Delete this engine', - } - ), - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (engine) => { - if ( - window.confirm( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.confirmationPopupMessage', - { - defaultMessage: - 'Are you sure you want to permanently delete "{engineName}" and all of its content?', - values: { - engineName: engine.name, - }, - } - ) - ) - ) { - EnginesLogic.actions.deleteEngine(engine); - } - }, - }, - ], -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/index.ts deleted file mode 100644 index c2989c5d1f972..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { runSharedColumnsTests } from './shared_columns'; -export { runSharedPropsTests } from './shared_props'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_columns.tsx deleted file mode 100644 index 8e57be1c798a1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_columns.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; -import '../__mocks__/engines_logic.mock'; - -import { ShallowWrapper } from 'enzyme'; - -import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; - -import { EnginesLogic } from '../../..'; -import { rerender } from '../../../../../../test_helpers'; - -import * as engineLinkHelpers from '../engine_link_helpers'; - -export const runSharedColumnsTests = ( - wrapper: ShallowWrapper, - tableContent: string, - values: object = {} -) => { - const simulatedClickEvent = { persist: () => {} }; // Required for EUI action clicks. Can be removed if switching away from Enzyme to RTL - - const getTableBody = () => - // @ts-expect-error upgrade typescript v5.1.6 - wrapper.find(EuiBasicTable).dive().find('RenderWithEuiTheme').renderProp('children')(); - - describe('name column', () => { - it('renders', () => { - expect(tableContent).toContain('test-engine'); - }); - - // Link behavior is tested in engine_link_helpers.test.tsx - }); - - describe('created at column', () => { - it('renders', () => { - expect(tableContent).toContain('Created at'); - expect(tableContent).toContain('Jan 1, 1970'); - }); - }); - - describe('document count column', () => { - it('renders', () => { - expect(tableContent).toContain('Document count'); - expect(tableContent).toContain('99,999'); - }); - }); - - describe('field count column', () => { - it('renders', () => { - expect(tableContent).toContain('Field count'); - expect(tableContent).toContain('10'); - }); - }); - - describe('actions column', () => { - const getActions = () => getTableBody().find('ExpandedItemActions'); - const getActionItems = () => getActions().dive().find('DefaultItemAction'); - - it('will hide the action buttons if the user cannot manage/delete engines', () => { - setMockValues({ - ...values, - myRole: { canManageEngines: false, canManageMetaEngines: false }, - }); - rerender(wrapper); - expect(getActions()).toHaveLength(0); - }); - - describe('when the user can manage/delete engines', () => { - const getManageAction = () => getActionItems().at(0).dive().find(EuiButtonIcon); - const getDeleteAction = () => getActionItems().at(1).dive().find(EuiButtonIcon); - - beforeAll(() => { - setMockValues({ - ...values, - myRole: { canManageEngines: true, canManageMetaEngines: true }, - }); - rerender(wrapper); - }); - - describe('manage action', () => { - it('sends the user to the engine overview on click', () => { - jest.spyOn(engineLinkHelpers, 'navigateToEngine'); - const { navigateToEngine } = engineLinkHelpers; - getManageAction().simulate('click', simulatedClickEvent); - - expect(navigateToEngine).toHaveBeenCalledWith('test-engine'); - }); - }); - - describe('delete action', () => { - const { deleteEngine } = EnginesLogic.actions; - - it('clicking the action and confirming deletes the engine', () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(true); - getDeleteAction().simulate('click', simulatedClickEvent); - - expect(deleteEngine).toHaveBeenCalledWith( - expect.objectContaining({ name: 'test-engine' }) - ); - }); - - it('clicking the action and not confirming does not delete the engine', () => { - jest.spyOn(global, 'confirm').mockReturnValueOnce(false); - getDeleteAction().simulate('click', simulatedClickEvent); - - expect(deleteEngine).not.toHaveBeenCalled(); - }); - }); - }); - }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_props.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_props.tsx deleted file mode 100644 index 0b0a8a0a99593..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_props.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { ShallowWrapper } from 'enzyme'; - -import { EuiBasicTable } from '@elastic/eui'; - -export const runSharedPropsTests = (wrapper: ShallowWrapper) => { - it('passes the loading prop', () => { - wrapper.setProps({ loading: true }); - expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true); - }); - - it('passes the noItemsMessage prop', () => { - wrapper.setProps({ noItemsMessage: 'No items.' }); - expect(wrapper.find(EuiBasicTable).prop('noItemsMessage')).toEqual('No items.'); - }); - - describe('pagination', () => { - it('passes the pagination prop', () => { - const pagination = { - pageIndex: 0, - pageSize: 10, - totalItemCount: 50, - }; - wrapper.setProps({ pagination }); - expect(wrapper.find(EuiBasicTable).prop('pagination')).toEqual(pagination); - }); - - it('triggers onChange', () => { - const onChange = jest.fn(); - wrapper.setProps({ onChange }); - - wrapper.find(EuiBasicTable).simulate('change', { page: { index: 4 } }); - expect(onChange).toHaveBeenCalledWith({ page: { index: 4 } }); - }); - }); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/types.ts deleted file mode 100644 index 8e3587c4440ab..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { ReactNode } from 'react'; - -import { CriteriaWithPagination } from '@elastic/eui'; - -import { EngineDetails } from '../../../engine/types'; - -export interface EnginesTableProps { - items: EngineDetails[]; - loading: boolean; - noItemsMessage?: ReactNode; - pagination: { - pageIndex: number; - pageSize: number; - totalItemCount: number; - showPerPageOptions: boolean; - }; - onChange(criteria: CriteriaWithPagination): void; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.test.ts deleted file mode 100644 index 2fbf108223fb0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 { SchemaConflictFieldTypes, SchemaConflicts } from '../../../../../shared/schema/types'; -import { EngineDetails } from '../../../engine/types'; - -import { - getConflictingEnginesFromConflictingField, - getConflictingEnginesFromSchemaConflicts, - getConflictingEnginesSet, -} from './utils'; - -describe('getConflictingEnginesFromConflictingField', () => { - const CONFLICTING_FIELD: SchemaConflictFieldTypes = { - text: ['source-engine-1'], - number: ['source-engine-2', 'source-engine-3'], - geolocation: ['source-engine-4'], - date: ['source-engine-5', 'source-engine-6'], - }; - - it('returns a flat array of all engines with conflicts across different schema types, including duplicates', () => { - const result = getConflictingEnginesFromConflictingField(CONFLICTING_FIELD); - - // we can't guarantee ordering - expect(result).toHaveLength(6); - expect(result).toContain('source-engine-1'); - expect(result).toContain('source-engine-2'); - expect(result).toContain('source-engine-3'); - expect(result).toContain('source-engine-4'); - expect(result).toContain('source-engine-5'); - expect(result).toContain('source-engine-6'); - }); -}); - -describe('getConflictingEnginesFromSchemaConflicts', () => { - it('returns a flat array of all engines with conflicts across all fields, including duplicates', () => { - const SCHEMA_CONFLICTS: SchemaConflicts = { - 'conflicting-field-1': { - text: ['source-engine-1'], - number: ['source-engine-2'], - geolocation: [], - date: [], - }, - 'conflicting-field-2': { - text: [], - number: [], - geolocation: ['source-engine-2'], - date: ['source-engine-3'], - }, - }; - - const result = getConflictingEnginesFromSchemaConflicts(SCHEMA_CONFLICTS); - - // we can't guarantee ordering - expect(result).toHaveLength(4); - expect(result).toContain('source-engine-1'); - expect(result).toContain('source-engine-2'); - expect(result).toContain('source-engine-3'); - }); -}); - -describe('getConflictingEnginesSet', () => { - const DEFAULT_META_ENGINE_DETAILS = { - name: 'test-engine-1', - includedEngines: [ - { - name: 'source-engine-1', - }, - { - name: 'source-engine-2', - }, - { - name: 'source-engine-3', - }, - ] as EngineDetails[], - schemaConflicts: { - 'conflicting-field-1': { - text: ['source-engine-1'], - number: ['source-engine-2'], - geolocation: [], - date: [], - }, - 'conflicting-field-2': { - text: [], - number: [], - geolocation: ['source-engine-2'], - date: ['source-engine-3'], - }, - } as SchemaConflicts, - } as EngineDetails; - - it('generates a set of engine names with any field conflicts for the meta-engine', () => { - expect(getConflictingEnginesSet(DEFAULT_META_ENGINE_DETAILS)).toEqual( - new Set(['source-engine-1', 'source-engine-2', 'source-engine-3']) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts deleted file mode 100644 index 13cad349d75b0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { SchemaConflictFieldTypes, SchemaConflicts } from '../../../../../shared/schema/types'; -import { EngineDetails } from '../../../engine/types'; - -export const getConflictingEnginesFromConflictingField = ( - conflictingField: SchemaConflictFieldTypes -) => Object.values(conflictingField).flat() as string[]; - -export const getConflictingEnginesFromSchemaConflicts = ( - schemaConflicts: SchemaConflicts -): string[] => Object.values(schemaConflicts).flatMap(getConflictingEnginesFromConflictingField); - -// Given a meta-engine (represented by IEngineDetails), generate a Set of all source engines -// who have schema conflicts in the context of that meta-engine -// -// A Set allows us to enforce uniqueness and has O(1) lookup time -export const getConflictingEnginesSet = (metaEngine: EngineDetails): Set => { - const conflictingEngines: string[] = metaEngine.schemaConflicts - ? getConflictingEnginesFromSchemaConflicts(metaEngine.schemaConflicts) - : []; - return new Set(conflictingEngines); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx deleted file mode 100644 index 47288726ae0f0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const ENGINES_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engines.title', { - defaultMessage: 'Engines', -}); - -export const ENGINES_OVERVIEW_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.title', - { defaultMessage: 'Engines overview' } -); - -export const META_ENGINES_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngines.title', - { defaultMessage: 'Meta Engines' } -); - -export const SOURCE_ENGINES_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.metaEnginesTable.sourceEngines.title', - { defaultMessage: 'Source Engines' } -); - -export const CREATE_AN_ENGINE_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engines.createEngineButtonLabel', - { - defaultMessage: 'Create engine', - } -); -export const CREATE_A_META_ENGINE_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engines.createMetaEngineButtonLabel', - { - defaultMessage: 'Create meta engine', - } -); - -export const DELETE_ENGINE_MESSAGE = (engineName: string) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.successMessage', - { - defaultMessage: "Engine ''{engineName}'' was deleted", - values: { - engineName, - }, - } - ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts deleted file mode 100644 index 522f79ccca7f1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { DEFAULT_META } from '../../../shared/constants'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; -import { EngineDetails, EngineTypes } from '../engine/types'; - -import { EnginesLogic } from '.'; - -describe('EnginesLogic', () => { - const { mount } = new LogicMounter(EnginesLogic); - const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - dataLoading: true, - engines: [], - enginesMeta: DEFAULT_META, - enginesLoading: true, - metaEngines: [], - metaEnginesMeta: DEFAULT_META, - metaEnginesLoading: true, - }; - - const MOCK_ENGINE = { - name: 'hello-world', - created_at: 'Fri, 1 Jan 1970 12:00:00 +0000', - document_count: 50, - field_count: 10, - } as EngineDetails; - const MOCK_META = { - page: { - current: 1, - size: 10, - total_results: 100, - total_pages: 10, - }, - }; - const MOCK_ENGINES_API_RESPONSE = { - results: [MOCK_ENGINE], - meta: MOCK_META, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(EnginesLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onEnginesLoad', () => { - it('should set engines & enginesMeta and set enginesLoading to false', () => { - mount(); - EnginesLogic.actions.onEnginesLoad(MOCK_ENGINES_API_RESPONSE); - - expect(EnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - engines: [MOCK_ENGINE], - enginesMeta: MOCK_META, - enginesLoading: false, - dataLoading: false, - }); - }); - }); - - describe('onMetaEnginesLoad', () => { - it('should set engines & enginesMeta and set enginesLoading to false', () => { - mount(); - EnginesLogic.actions.onMetaEnginesLoad(MOCK_ENGINES_API_RESPONSE); - - expect(EnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - metaEngines: [MOCK_ENGINE], - metaEnginesMeta: MOCK_META, - metaEnginesLoading: false, - }); - }); - }); - - describe('onEnginesPagination', () => { - it('should set enginesMeta.page.current', () => { - mount(); - EnginesLogic.actions.onEnginesPagination(2); - - expect(EnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - enginesMeta: { - page: { - ...DEFAULT_VALUES.enginesMeta.page, - current: 2, - }, - }, - }); - }); - }); - - describe('onMetaEnginesPagination', () => { - it('should set metaEnginesMeta.page.current', () => { - mount(); - EnginesLogic.actions.onMetaEnginesPagination(99); - - expect(EnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - metaEnginesMeta: { - page: { - ...DEFAULT_VALUES.metaEnginesMeta.page, - current: 99, - }, - }, - }); - }); - }); - }); - - describe('listeners', () => { - describe('deleteEngine', () => { - it('calls the engine API endpoint then onDeleteEngineSuccess', async () => { - http.delete.mockReturnValueOnce(Promise.resolve({})); - mount(); - jest.spyOn(EnginesLogic.actions, 'onDeleteEngineSuccess'); - - EnginesLogic.actions.deleteEngine(MOCK_ENGINE); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith('/internal/app_search/engines/hello-world'); - expect(EnginesLogic.actions.onDeleteEngineSuccess).toHaveBeenCalledWith(MOCK_ENGINE); - }); - - it('calls flashAPIErrors on API Error', async () => { - http.delete.mockReturnValueOnce(Promise.reject()); - mount(); - - EnginesLogic.actions.deleteEngine(MOCK_ENGINE); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - }); - }); - - describe('loadEngines', () => { - it('should call the engines API endpoint and set state based on the results', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_ENGINES_API_RESPONSE)); - mount(); - jest.spyOn(EnginesLogic.actions, 'onEnginesLoad'); - - EnginesLogic.actions.loadEngines(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines', { - query: { - type: 'indexed', - 'page[current]': 1, - 'page[size]': 10, - }, - }); - expect(EnginesLogic.actions.onEnginesLoad).toHaveBeenCalledWith(MOCK_ENGINES_API_RESPONSE); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - EnginesLogic.actions.loadEngines(); - }); - }); - - describe('loadMetaEngines', () => { - it('should call the engines API endpoint and set state based on the results', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_ENGINES_API_RESPONSE)); - mount(); - jest.spyOn(EnginesLogic.actions, 'onMetaEnginesLoad'); - - EnginesLogic.actions.loadMetaEngines(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines', { - query: { - type: 'meta', - 'page[current]': 1, - 'page[size]': 10, - }, - }); - expect(EnginesLogic.actions.onMetaEnginesLoad).toHaveBeenCalledWith( - MOCK_ENGINES_API_RESPONSE - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - EnginesLogic.actions.loadMetaEngines(); - }); - }); - - describe('onDeleteEngineSuccess', () => { - beforeEach(() => { - mount(); - }); - - it('should call flashSuccessToast', () => { - EnginesLogic.actions.onDeleteEngineSuccess(MOCK_ENGINE); - - expect(flashSuccessToast).toHaveBeenCalled(); - }); - - it('should call loadEngines if engine.type === default', () => { - jest.spyOn(EnginesLogic.actions, 'loadEngines'); - - EnginesLogic.actions.onDeleteEngineSuccess({ - ...MOCK_ENGINE, - type: 'default' as EngineTypes.default, - }); - - expect(EnginesLogic.actions.loadEngines).toHaveBeenCalled(); - }); - - it('should call loadEngines if engine.type === elasticsearch', () => { - jest.spyOn(EnginesLogic.actions, 'loadEngines'); - - EnginesLogic.actions.onDeleteEngineSuccess({ - ...MOCK_ENGINE, - type: 'elasticsearch' as EngineTypes.elasticsearch, - }); - - expect(EnginesLogic.actions.loadEngines).toHaveBeenCalled(); - }); - - it('should call loadMetaEngines if engine.type === meta', () => { - jest.spyOn(EnginesLogic.actions, 'loadMetaEngines'); - - EnginesLogic.actions.onDeleteEngineSuccess({ - ...MOCK_ENGINE, - type: 'meta' as EngineTypes.meta, - }); - - expect(EnginesLogic.actions.loadMetaEngines).toHaveBeenCalled(); - }); - }); - }); - - describe('selectors', () => { - describe('dataLoading', () => { - it('returns true if enginesLoading is true and engines is empty', () => { - mount({ - enginesLoading: true, - engines: [], - }); - expect(EnginesLogic.values.dataLoading).toEqual(true); - }); - - it('returns false if enginesLoading is true but engines exist', () => { - // = the engines table is paginating, which has its own separate table loading indicator - mount({ - enginesLoading: true, - engines: [MOCK_ENGINE], - }); - expect(EnginesLogic.values.dataLoading).toEqual(false); - }); - - it('returns false if engineLoading is false and engines is empty', () => { - // = empty prompt state - mount({ - enginesLoading: false, - engines: [], - }); - expect(EnginesLogic.values.dataLoading).toEqual(false); - }); - - // NOTE: dataLoading ignores metaEnginesLoading to prevent a race condition where - // meta engines finish fetching before engines and flash an empty prompt state. - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts deleted file mode 100644 index 487a52fd7814b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; -import { DEFAULT_META } from '../../../shared/constants'; -import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { updateMetaPageIndex } from '../../../shared/table_pagination'; - -import { EngineDetails, EngineTypes } from '../engine/types'; - -import { DELETE_ENGINE_MESSAGE } from './constants'; -import { EnginesAPIResponse } from './types'; - -interface EnginesValues { - dataLoading: boolean; - engines: EngineDetails[]; - enginesMeta: Meta; - enginesLoading: boolean; - metaEngines: EngineDetails[]; - metaEnginesMeta: Meta; // keanu_whoa.jpg - metaEnginesLoading: boolean; -} - -interface EnginesActions { - deleteEngine(engine: EngineDetails): { engine: EngineDetails }; - onDeleteEngineSuccess(engine: EngineDetails): { engine: EngineDetails }; - onEnginesLoad({ results, meta }: EnginesAPIResponse): EnginesAPIResponse; - onMetaEnginesLoad({ results, meta }: EnginesAPIResponse): EnginesAPIResponse; - onEnginesPagination(page: number): { page: number }; - onMetaEnginesPagination(page: number): { page: number }; - loadEngines(): void; - loadMetaEngines(): void; -} - -export const EnginesLogic = kea>({ - path: ['enterprise_search', 'app_search', 'engines_logic'], - actions: { - deleteEngine: (engine) => ({ engine }), - onDeleteEngineSuccess: (engine) => ({ engine }), - onEnginesLoad: ({ results, meta }) => ({ results, meta }), - onMetaEnginesLoad: ({ results, meta }) => ({ results, meta }), - onEnginesPagination: (page) => ({ page }), - onMetaEnginesPagination: (page) => ({ page }), - loadEngines: true, - loadMetaEngines: true, - }, - reducers: { - engines: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onEnginesLoad: (_, { results }) => results, - }, - ], - enginesMeta: [ - DEFAULT_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - onEnginesLoad: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onEnginesPagination: (state, { page }) => updateMetaPageIndex(state, page), - }, - ], - enginesLoading: [ - true, - { - loadEngines: () => true, - onEnginesLoad: () => false, - }, - ], - metaEngines: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onMetaEnginesLoad: (_, { results }) => results, - }, - ], - metaEnginesMeta: [ - DEFAULT_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - onMetaEnginesLoad: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onMetaEnginesPagination: (state, { page }) => updateMetaPageIndex(state, page), - }, - ], - metaEnginesLoading: [ - true, - { - loadMetaEngines: () => true, - onMetaEnginesLoad: () => false, - }, - ], - }, - selectors: { - dataLoading: [ - (selectors) => [selectors.enginesLoading, selectors.engines], - (enginesLoading, engines) => enginesLoading && !engines.length, - ], - }, - listeners: ({ actions, values }) => ({ - deleteEngine: async ({ engine }) => { - const { http } = HttpLogic.values; - let response; - - try { - response = await http.delete(`/internal/app_search/engines/${engine.name}`); - } catch (e) { - flashAPIErrors(e); - } - - if (response) { - actions.onDeleteEngineSuccess(engine); - } - }, - loadEngines: async () => { - const { http } = HttpLogic.values; - const { enginesMeta } = values; - - try { - const response = await http.get('/internal/app_search/engines', { - query: { - type: 'indexed', - 'page[current]': enginesMeta.page.current, - 'page[size]': enginesMeta.page.size, - }, - }); - actions.onEnginesLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - loadMetaEngines: async () => { - const { http } = HttpLogic.values; - const { metaEnginesMeta } = values; - - try { - const response = await http.get('/internal/app_search/engines', { - query: { - type: 'meta', - 'page[current]': metaEnginesMeta.page.current, - 'page[size]': metaEnginesMeta.page.size, - }, - }); - actions.onMetaEnginesLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - onDeleteEngineSuccess: async ({ engine }) => { - flashSuccessToast(DELETE_ENGINE_MESSAGE(engine.name)); - if ( - [EngineTypes.default, EngineTypes.indexed, EngineTypes.elasticsearch].includes(engine.type) - ) { - actions.loadEngines(); - } else if (engine.type === EngineTypes.meta) { - actions.loadMetaEngines(); - } - }, - }), -}); 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 deleted file mode 100644 index 164e0131ca57b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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 '../../../__mocks__/shallow_useeffect.mock'; -import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -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'; - -import { EnginesOverview } from '.'; - -describe('EnginesOverview', () => { - const values = { - account: { - kibanaUIsEnabled: true, - role: { - roleType: 'owner', - }, - }, - dataLoading: false, - engines: [], - enginesMeta: { - page: { - current: 1, - size: 10, - total_results: 0, - }, - }, - enginesLoading: false, - metaEngines: [], - metaEnginesMeta: { - page: { - current: 1, - size: 10, - total_results: 0, - }, - }, - metaEnginesLoading: false, - hasPlatinumLicense: false, - // AppLogic - myRole: { canManageEngines: false, canManageMetaEngines: false }, - // MetaEnginesTableLogic - expandedSourceEngines: {}, - conflictingEnginesSets: {}, - showGateForm: false, - }; - - const actions = { - loadEngines: jest.fn(), - loadMetaEngines: jest.fn(), - onEnginesPagination: jest.fn(), - onMetaEnginesPagination: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - const valuesWithEngines = { - ...values, - dataLoading: false, - engines: ['test-engine'], - enginesMeta: { - page: { - current: 1, - size: 10, - total_results: 100, - }, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(valuesWithEngines); - }); - - 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(); - }); - - describe('engine creation', () => { - it('renders a create engine action when the users can create engines', () => { - setMockValues({ - ...valuesWithEngines, - myRole: { canManageEngines: true }, - }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="appSearchEngines"]').prop('action')).toBeTruthy(); - }); - - it('does not render a create engine action if the user cannot create engines', () => { - setMockValues({ - ...valuesWithEngines, - myRole: { canManageEngines: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="appSearchEngines"]').prop('action')).toBeFalsy(); - }); - }); - - describe('when the account has a platinum license', () => { - it('renders a 2nd meta engines table & makes a 2nd meta engines call', () => { - setMockValues({ - ...valuesWithEngines, - hasPlatinumLicense: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(MetaEnginesTable)).toHaveLength(1); - expect(actions.loadMetaEngines).toHaveBeenCalled(); - }); - - it('renders meta engines even if there is no indexed engine', () => { - setMockValues({ - ...valuesWithEngines, - hasPlatinumLicense: true, - engines: [], - }); - const wrapper = shallow(); - - expect(wrapper.find(MetaEnginesTable)).toHaveLength(1); - expect(actions.loadMetaEngines).toHaveBeenCalled(); - }); - - describe('meta engine creation', () => { - it('renders a create meta engine action when the user can create meta engines', () => { - setMockValues({ - ...valuesWithEngines, - hasPlatinumLicense: true, - myRole: { canManageMetaEngines: true }, - }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="appSearchMetaEngines"]').prop('action')).toBeTruthy(); - }); - - it('does not render a create meta engine action if user cannot create meta engines', () => { - setMockValues({ - ...valuesWithEngines, - hasPlatinumLicense: true, - myRole: { canManageMetaEngines: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="appSearchMetaEngines"]').prop('action')).toBeFalsy(); - }); - }); - }); - - describe('pagination', () => { - const getTablePagination = (wrapper: ShallowWrapper) => - wrapper.find(EnginesTable).prop('pagination'); - - it('passes down page data from the API', () => { - const wrapper = shallow(); - const pagination = getTablePagination(wrapper); - - expect(pagination.totalItemCount).toEqual(100); - expect(pagination.pageIndex).toEqual(0); - }); - - it('re-polls the API on page change', () => { - const wrapper = shallow(); - - setMockValues({ - ...valuesWithEngines, - enginesMeta: { - page: { - ...valuesWithEngines.enginesMeta.page, - current: 51, - }, - }, - }); - rerender(wrapper); - - expect(actions.loadEngines).toHaveBeenCalledTimes(2); - expect(getTablePagination(wrapper).pageIndex).toEqual(50); - }); - - it('calls onPagination handlers', () => { - setMockValues({ - ...valuesWithEngines, - hasPlatinumLicense: true, - metaEngines: ['test-meta-engine'], - }); - const wrapper = shallow(); - const pageEvent = { page: { index: 0 } }; - - wrapper.find(EnginesTable).simulate('change', pageEvent); - expect(actions.onEnginesPagination).toHaveBeenCalledWith(1); - - wrapper.find(MetaEnginesTable).simulate('change', pageEvent); - expect(actions.onMetaEnginesPagination).toHaveBeenCalledWith(1); - }); - }); -}); 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 deleted file mode 100644 index 339924ec7b3d9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiSpacer } from '@elastic/eui'; - -import { EuiButtonTo } from '../../../shared/react_router_helpers'; -import { convertMetaToPagination, handlePageChange } from '../../../shared/table_pagination'; -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'; - -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, - CREATE_A_META_ENGINE_BUTTON_LABEL, - ENGINES_TITLE, - META_ENGINES_TITLE, -} from './constants'; -import { EnginesLogic } from './engines_logic'; - -export const EnginesOverview: React.FC = () => { - const { - showGateForm, - myRole: { canManageEngines, canManageMetaEngines }, - } = useValues(AppLogic); - - const { - dataLoading, - engines, - enginesMeta, - enginesLoading, - metaEngines, - metaEnginesMeta, - metaEnginesLoading, - } = useValues(EnginesLogic); - - const { loadEngines, loadMetaEngines, onEnginesPagination, onMetaEnginesPagination } = - useActions(EnginesLogic); - - useEffect(() => { - loadEngines(); - }, [enginesMeta.page.current]); - - useEffect(() => { - loadMetaEngines(); - }, [metaEnginesMeta.page.current]); - - return !showGateForm ? ( - } - > - {ENGINES_TITLE}} - titleSize="s" - action={ - canManageEngines && ( - - {CREATE_AN_ENGINE_BUTTON_LABEL} - - ) - } - data-test-subj="appSearchEngines" - > - - - - {META_ENGINES_TITLE}} - titleSize="s" - action={ - canManageMetaEngines && ( - - {CREATE_A_META_ENGINE_BUTTON_LABEL} - - ) - } - data-test-subj="appSearchMetaEngines" - > - } - onChange={handlePageChange(onMetaEnginesPagination)} - /> - - - - ) : ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts deleted file mode 100644 index 9372cdc92bce6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { EnginesLogic } from './engines_logic'; -export { EnginesOverview } from './engines_overview'; -export { ENGINES_TITLE, META_ENGINES_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/types.ts deleted file mode 100644 index 95b507954b8d4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; -import { EngineDetails } from '../engine/types'; - -export interface EnginesAPIResponse { - results: EngineDetails[]; - meta: Meta; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/index.ts deleted file mode 100644 index a7699848831b2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { AppSearchPageTemplate } from './page_template'; -export { useAppSearchNav } from './nav'; -export { KibanaHeaderActions } from './kibana_header_actions'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/kibana_header_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/kibana_header_actions.test.tsx deleted file mode 100644 index d25e6ab27b099..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/kibana_header_actions.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { QueryTesterButton } from '../query_tester'; - -import { KibanaHeaderActions } from './kibana_header_actions'; - -describe('KibanaHeaderActions', () => { - const values = { - engineName: 'foo', - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(QueryTesterButton).exists()).toBe(true); - }); - - it('does not render a "Query Tester" button if there is no engine available', () => { - setMockValues({ - engineName: '', - }); - const wrapper = shallow(); - expect(wrapper.find(QueryTesterButton).exists()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/kibana_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/kibana_header_actions.tsx deleted file mode 100644 index e2756bdda05f2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/kibana_header_actions.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EndpointsHeaderAction } from '../../../shared/layout/endpoints_header_action'; -import { EngineLogic } from '../engine'; -import { QueryTesterButton } from '../query_tester'; - -export const KibanaHeaderActions: React.FC = () => { - const { engineName } = useValues(EngineLogic); - - return ( - {Boolean(engineName) && } - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx deleted file mode 100644 index ce4a118bef095..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.test.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; - -jest.mock('../../../shared/layout', () => ({ - generateNavLink: jest.fn(({ to, items }) => ({ href: to, items })), -})); -jest.mock('../engine/engine_nav', () => ({ - useEngineNav: () => [], -})); - -import { useAppSearchNav } from './nav'; - -describe('useAppSearchNav', () => { - it('always generates a default engines nav item', () => { - setMockValues({ myRole: {} }); - - expect(useAppSearchNav()).toEqual([ - { - id: '', - name: '', - items: [ - { - id: 'engines', - name: 'Engines', - href: '/engines', - items: [], - }, - ], - }, - ]); - }); - - it('generates a settings nav item if the user can view settings', () => { - setMockValues({ myRole: { canViewSettings: true } }); - - expect(useAppSearchNav()).toEqual([ - { - id: '', - name: '', - items: [ - { - id: 'engines', - name: 'Engines', - href: '/engines', - items: [], - }, - { - id: 'settings', - name: 'Settings', - href: '/settings', - }, - ], - }, - ]); - }); - - it('generates a credentials nav item if the user can view credentials', () => { - setMockValues({ myRole: { canViewAccountCredentials: true } }); - - expect(useAppSearchNav()).toEqual([ - { - id: '', - name: '', - items: [ - { - id: 'engines', - name: 'Engines', - href: '/engines', - items: [], - }, - { - id: 'credentials', - name: 'Credentials', - href: '/credentials', - }, - ], - }, - ]); - }); - - it('generates a users & roles nav item if the user can view role mappings', () => { - setMockValues({ myRole: { canViewRoleMappings: true } }); - - expect(useAppSearchNav()).toEqual([ - { - id: '', - name: '', - items: [ - { - id: 'engines', - name: 'Engines', - href: '/engines', - items: [], - }, - { - id: 'usersRoles', - name: 'Users and roles', - href: '/users_and_roles', - }, - ], - }, - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx deleted file mode 100644 index 20bbfe38e5f39..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/nav.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiSideNavItemType } from '@elastic/eui'; - -import { generateNavLink } from '../../../shared/layout'; -import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; - -import { AppLogic } from '../../app_logic'; -import { ENGINES_PATH, SETTINGS_PATH, CREDENTIALS_PATH, USERS_AND_ROLES_PATH } from '../../routes'; -import { CREDENTIALS_TITLE } from '../credentials'; -import { useEngineNav } from '../engine/engine_nav'; -import { ENGINES_TITLE } from '../engines'; -import { SETTINGS_TITLE } from '../settings'; - -export const useAppSearchNav = () => { - const { - myRole: { canViewSettings, canViewAccountCredentials, canViewRoleMappings }, - } = useValues(AppLogic); - - const navItems: Array> = [ - { - id: 'engines', - name: ENGINES_TITLE, - ...generateNavLink({ - to: ENGINES_PATH, - shouldShowActiveForSubroutes: true, - items: useEngineNav(), - }), - }, - ]; - - if (canViewSettings) { - navItems.push({ - id: 'settings', - name: SETTINGS_TITLE, - ...generateNavLink({ to: SETTINGS_PATH }), - }); - } - - if (canViewAccountCredentials) { - navItems.push({ - id: 'credentials', - name: CREDENTIALS_TITLE, - ...generateNavLink({ to: CREDENTIALS_PATH }), - }); - } - - if (canViewRoleMappings) { - navItems.push({ - id: 'usersRoles', - name: ROLE_MAPPINGS_TITLE, - ...generateNavLink({ to: USERS_AND_ROLES_PATH }), - }); - } - - // Root level items are meant to be section headers, but the AS nav (currently) - // isn't organized this way. So we create a fake empty parent item here - // to cause all our navItems to properly render as nav links. - return [{ id: '', name: '', items: navItems }]; -}; 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 deleted file mode 100644 index 2cd48283a2859..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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. - */ - -jest.mock('./nav', () => ({ - useAppSearchNav: () => [], -})); -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; -import { of } from 'rxjs'; - -import { SetAppSearchChrome } from '../../../shared/kibana_chrome'; -import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; -import { SendAppSearchTelemetry } from '../../../shared/telemetry'; - -import { AppSearchPageTemplate } from './page_template'; - -const mockValues = { - getChromeStyle$: () => of('classic'), - updateSideNavDefinition: jest.fn(), -}; - -describe('AppSearchPageTemplate', () => { - beforeEach(() => { - setMockValues({ ...mockValues }); - }); - - it('renders', () => { - const wrapper = shallow( - -
    world
    -
    - ); - - expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'App Search', items: [] }); - expect(wrapper.find('.hello').text()).toEqual('world'); - }); - - describe('page chrome', () => { - it('takes a breadcrumb array & renders a product-specific page chrome', () => { - const wrapper = shallow(); - const setPageChrome = wrapper - .find(EnterpriseSearchPageTemplateWrapper) - .prop('setPageChrome') as any; - - expect(setPageChrome.type).toEqual(SetAppSearchChrome); - expect(setPageChrome.props.trail).toEqual(['Some page']); - }); - }); - - describe('page telemetry', () => { - it('takes a metric & renders product-specific telemetry viewed event', () => { - const wrapper = shallow(); - - expect(wrapper.find(SendAppSearchTelemetry).prop('action')).toEqual('viewed'); - expect(wrapper.find(SendAppSearchTelemetry).prop('metric')).toEqual('some_page'); - }); - }); - - it('passes down any ...pageTemplateProps that EnterpriseSearchPageTemplateWrapper accepts', () => { - const wrapper = shallow( - } - /> - ); - - expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('pageHeader')!.pageTitle).toEqual( - 'hello world' - ); - expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false); - expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(
    ); - }); -}); 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 deleted file mode 100644 index 5168a05c8a376..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/layout/page_template.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { useValues } from 'kea'; -import useObservable from 'react-use/lib/useObservable'; - -import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { KibanaLogic } from '../../../shared/kibana'; -import { SetAppSearchChrome } from '../../../shared/kibana_chrome'; -import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout'; -import { SendAppSearchTelemetry } from '../../../shared/telemetry'; - -import { useAppSearchNav } from './nav'; - -export const AppSearchPageTemplate: React.FC< - Omit -> = ({ children, pageChrome, pageViewTelemetry, ...pageTemplateProps }) => { - const navItems = useAppSearchNav(); - const { getChromeStyle$ } = useValues(KibanaLogic); - const chromeStyle = useObservable(getChromeStyle$(), 'classic'); - - return ( - } - useEndpointHeaderActions={false} - hideEmbeddedConsole - > - {pageViewTelemetry && } - {children} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/index.ts deleted file mode 100644 index 8785fc3218b7c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { Library } from './library'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx deleted file mode 100644 index 24ab03d5e93a7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ /dev/null @@ -1,547 +0,0 @@ -/* - * 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. - */ - -/* istanbul ignore file */ - -import React, { useState } from 'react'; - -import { - EuiSpacer, - EuiPageHeader, - EuiTitle, - EuiPageSection, - EuiDragDropContext, - EuiDroppable, - EuiDraggable, - EuiButtonIconProps, - EuiEmptyPrompt, - EuiPageBody, -} from '@elastic/eui'; - -import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { Schema, SchemaType } from '../../../shared/schema/types'; -import { InlineEditableTable } from '../../../shared/tables/inline_editable_table'; -import { ReorderableTable } from '../../../shared/tables/reorderable_table'; -import { Result } from '../result'; - -const NO_ITEMS = ( - No Items} body={

    No Items

    } /> -); - -// For the InlineEditableTable -// Since InlineEditableTable caches handlers, we need to store this globally somewhere rather than just in useState -interface Foo { - id: number; - foo: string; - bar: string; -} -let globalItems: Foo[] = []; -const getLastItems = () => globalItems; - -export const Library: React.FC = () => { - const props = { - isMetaEngine: false, - result: { - id: { - raw: '1', - }, - _meta: { - id: '1', - score: 100, - engine: 'my-engine', - }, - title: { - raw: 'A title', - }, - description: { - raw: 'A description', - }, - date_established: { - raw: '1968-10-02T05:00:00Z', - }, - location: { - raw: '37.3,-113.05', - }, - visitors: { - raw: 1000, - }, - states: { - raw: ['Pennsylvania', 'Ohio'], - }, - size: { - raw: 200, - }, - length: { - raw: 100, - }, - }, - }; - - const schema: Schema = { - title: SchemaType.Text, - description: SchemaType.Text, - date_established: SchemaType.Date, - location: SchemaType.Geolocation, - states: SchemaType.Text, - visitors: SchemaType.Number, - size: SchemaType.Number, - length: SchemaType.Number, - }; - - const [isActionButtonFilled, setIsActionButtonFilled] = useState(false); - const actions = [ - { - title: 'Fill this action button', - onClick: () => setIsActionButtonFilled(!isActionButtonFilled), - iconType: isActionButtonFilled ? 'starFilled' : 'starEmpty', - iconColor: 'primary' as EuiButtonIconProps['color'], - }, - ]; - - // For the InlineEditableTable - const [items, setItems] = useState([ - { id: 1, foo: 'foo1', bar: '10' }, - { id: 2, foo: 'foo2', bar: '10' }, - ]); - globalItems = items; - const columns = [ - { - field: 'foo', - name: 'Foo', - render: (item: Foo) =>
    {item.foo}
    , - editingRender: (item: Foo, onChange: (value: string) => void) => ( - onChange(e.target.value)} /> - ), - }, - { - field: 'bar', - name: 'Bar (Must be a number)', - render: (item: Foo) =>
    {item.bar}
    , - editingRender: (item: Foo, onChange: (value: string) => void) => ( - onChange(e.target.value)} /> - ), - }, - ]; - const onAdd = (item: Foo, onSuccess: () => void) => { - const highestId = Math.max(...getLastItems().map((i) => i.id)); - setItems([ - ...getLastItems(), - { - ...item, - id: highestId + 1, - }, - ]); - onSuccess(); - }; - const onDelete = (item: Foo) => { - setItems(getLastItems().filter((i) => i.id !== item.id)); - }; - const onUpdate = (item: Foo, onSuccess: () => void) => { - setItems( - getLastItems().map((i) => { - if (item.id === i.id) return item; - return i; - }) - ); - onSuccess(); - }; - const validateItem = (item: Foo) => { - let isValidNumber = false; - const num = parseInt(item.bar, 10); - if (!isNaN(num)) isValidNumber = true; - - if (isValidNumber) return {}; - return { - bar: 'Bar must be a valid number', - }; - }; - const onReorder = (newItems: Foo[]) => { - setItems(newItems); - }; - - return ( - <> - - - - - -

    Result

    -
    - - - -

    5 or more fields

    -
    - - - - - -

    5 or less fields

    -
    - - - - - -

    With just an id

    -
    - - - - - -

    With an id and a score

    -
    - - - - - -

    With a document position

    -
    - - - - - -

    With an id and a score and an engine

    -
    - - - - - -

    With a long id, a long engine name, a long field key, and a long value

    -
    - - - - - -

    With a link

    -
    - - - - - - -

    With custom actions

    -
    - - - - - - -

    With custom actions and a link

    -
    - - - - - - -

    With a drag handle

    -
    - - {}}> - - {[1, 2, 3].map((_, i) => ( - - {(provided) => } - - ))} - - - - - -

    With field value type highlights

    -
    - - - - - -

    ReorderableTable

    -
    - - -
    {item.id}
    }, - { name: 'Whatever', render: () =>
    Whatever
    }, - ]} - /> - - - -

    With reordering disabled

    -
    - -
    {item.id}
    }, - { name: 'Whatever', render: () =>
    Whatever
    }, - ]} - /> - - - -

    With reordering enabled, but dragging disabled

    -
    - -
    {item.id}
    }, - { name: 'Whatever', render: () =>
    Whatever
    }, - ]} - /> - - - -

    With unreorderable items

    -
    - -
    {item.id}
    }, - { name: 'Whatever', render: () =>
    Whatever
    }, - ]} - /> - - - -

    Using the rowProps prop to apply dynamic properties to each row

    -
    - - ({ - style: { - backgroundColor: item.id % 2 === 0 ? 'red' : 'green', - }, - })} - noItemsMessage={NO_ITEMS} - items={[{ id: 1 }, { id: 2 }, { id: 3 }]} - columns={[ - { name: 'ID', render: (item) =>
    {item.id}
    }, - { name: 'Whatever', render: () =>
    Whatever
    }, - ]} - /> - - - -

    With no items

    -
    - -
    {item.id}
    }, - { name: 'Whatever', render: () =>
    Whatever
    }, - ]} - /> - - - -

    InlineEditableTable

    -
    - - - -

    With uneditable items

    -
    - - - - - -

    Can delete last item

    -
    - - { - return ( - No Items} - body={} - /> - ); - }} - /> - - - -

    Cannot delete last item

    -
    - - - - - -

    When isLoading is true

    -
    - - - - - - -
    -
    - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/index.ts deleted file mode 100644 index 0a6fcafdcca9e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { LogRetentionCallout } from './log_retention_callout'; -export { LogRetentionTooltip } from './log_retention_tooltip'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx deleted file mode 100644 index 1e54d4cdff51f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCallOut, EuiLink } from '@elastic/eui'; - -import { LogRetentionOptions } from '..'; -import { mountWithIntl } from '../../../../test_helpers'; - -import { LogRetentionCallout } from '.'; - -describe('LogRetentionCallout', () => { - const actions = { fetchLogRetention: jest.fn() }; - const values = { myRole: { canManageLogSettings: true } }; - const DISABLED = { - disabledAt: '01 Jan 1970 12:00:00 +0000', - enabled: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders an analytics callout', () => { - setMockValues({ ...values, logRetention: { analytics: DISABLED } }); - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find('.euiCallOutHeader__title').last().text()).toEqual( - 'Analytics have been disabled since January 1, 1970.' - ); - expect(wrapper.find(EuiLink)).toHaveLength(1); - expect(wrapper.find('p').last().text()).toEqual( - 'To manage analytics & logging, visit your settings.' - ); - }); - - it('renders an API callout', () => { - setMockValues({ ...values, logRetention: { api: DISABLED } }); - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find('.euiCallOutHeader__title').last().text()).toEqual( - 'API Logs have been disabled since January 1, 1970.' - ); - expect(wrapper.find(EuiLink)).toHaveLength(1); - expect(wrapper.find('p').last().text()).toEqual( - 'To manage analytics & logging, visit your settings.' - ); - }); - - it('renders a generic title if no disabled date is present', () => { - setMockValues({ ...values, logRetention: { api: { enabled: false, disabledAt: null } } }); - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find('.euiCallOutHeader__title').last().text()).toEqual( - 'API Logs have been disabled.' - ); - }); - - it('does not render if log retention is enabled', () => { - setMockValues({ ...values, logRetention: { api: { enabled: true } } }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('does not render if log retention is not available', () => { - setMockValues({ ...values, logRetention: null }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - describe('on mount', () => { - it('fetches log retention data when not already loaded', () => { - setMockValues({ ...values, logRetention: null }); - shallow(); - - expect(actions.fetchLogRetention).toHaveBeenCalled(); - }); - - it('does not fetch log retention data if it has already been loaded', () => { - setMockValues({ ...values, logRetention: {} }); - shallow(); - - expect(actions.fetchLogRetention).not.toHaveBeenCalled(); - }); - - it('does not fetch log retention data if the user does not have access to log settings', () => { - setMockValues({ ...values, logRetention: null, myRole: { canManageLogSettings: false } }); - shallow(); - - expect(actions.fetchLogRetention).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx deleted file mode 100644 index 8be371aac22b3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_callout.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { LogRetentionLogic, LogRetentionOptions, renderLogRetentionDate } from '..'; -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; - -import { AppLogic } from '../../../app_logic'; -import { SETTINGS_PATH } from '../../../routes'; -import { ANALYTICS_TITLE } from '../../analytics'; -import { API_LOGS_TITLE } from '../../api_logs'; -import { CRAWLER_TITLE } from '../../crawler'; - -export const AUDIT_LOGS_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.audit.title', { - defaultMessage: 'Audit', -}); - -const TITLE_MAP = { - [LogRetentionOptions.Analytics]: ANALYTICS_TITLE, - [LogRetentionOptions.API]: API_LOGS_TITLE, - [LogRetentionOptions.Audit]: AUDIT_LOGS_TITLE, - [LogRetentionOptions.Crawler]: CRAWLER_TITLE, -}; - -interface Props { - type: LogRetentionOptions; -} -export const LogRetentionCallout: React.FC = ({ type }) => { - const { fetchLogRetention } = useActions(LogRetentionLogic); - const { logRetention } = useValues(LogRetentionLogic); - const { - myRole: { canManageLogSettings }, - } = useValues(AppLogic); - - const hasLogRetention = logRetention !== null; - - useEffect(() => { - if (!hasLogRetention && canManageLogSettings) fetchLogRetention(); - }, []); - - const logRetentionSettings = logRetention?.[type]; - const title = TITLE_MAP[type]; - const hasLogRetentionDisabled = hasLogRetention && !logRetentionSettings?.enabled; - - return hasLogRetentionDisabled ? ( - <> - - ) : ( - i18n.translate('xpack.enterpriseSearch.appSearch.logRetention.callout.disabledTitle', { - defaultMessage: '{logsTitle} have been disabled.', - values: { - logsTitle: title, - }, - }) - ) - } - > -

    - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.callout.description.manageSettingsLinkText', - { defaultMessage: 'visit your settings' } - )} - - ), - }} - /> -

    -
    - - - ) : null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx deleted file mode 100644 index f42dc80fe235d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, mount } from 'enzyme'; - -import { EuiIconTip } from '@elastic/eui'; - -import { LogRetentionOptions, LogRetentionMessage } from '..'; - -import { LogRetentionTooltip } from '.'; - -describe('LogRetentionTooltip', () => { - const values = { - logRetention: {}, - myRole: { canManageLogSettings: true }, - }; - const actions = { fetchLogRetention: jest.fn() }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders an analytics tooltip', () => { - const wrapper = shallow(); - const tooltipContent = wrapper.find(EuiIconTip).prop('content') as React.ReactElement; - - expect(tooltipContent.type).toEqual(LogRetentionMessage); - expect(tooltipContent.props.type).toEqual('analytics'); - }); - - it('renders an API tooltip', () => { - const wrapper = shallow(); - const tooltipContent = wrapper.find(EuiIconTip).prop('content') as React.ReactElement; - - expect(tooltipContent.type).toEqual(LogRetentionMessage); - expect(tooltipContent.props.type).toEqual('api'); - }); - - it('passes custom tooltip positions', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiIconTip).prop('position')).toEqual('bottom'); - - wrapper.setProps({ position: 'right' }); - expect(wrapper.find(EuiIconTip).prop('position')).toEqual('right'); - }); - - it('does not render if log retention is not available', () => { - setMockValues({ ...values, logRetention: null }); - const wrapper = mount(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - describe('on mount', () => { - it('fetches log retention data when not already loaded', () => { - setMockValues({ ...values, logRetention: null }); - shallow(); - - expect(actions.fetchLogRetention).toHaveBeenCalled(); - }); - - it('does not fetch log retention data if it has already been loaded', () => { - setMockValues({ ...values, logRetention: {} }); - shallow(); - - expect(actions.fetchLogRetention).not.toHaveBeenCalled(); - }); - - it('does not fetch log retention data if the user does not have access to log settings', () => { - setMockValues({ ...values, logRetention: null, myRole: { canManageLogSettings: false } }); - shallow(); - - expect(actions.fetchLogRetention).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx deleted file mode 100644 index 98c5c95978a1d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/components/log_retention_tooltip.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiIconTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { LogRetentionLogic, LogRetentionMessage, LogRetentionOptions } from '..'; -import { AppLogic } from '../../../app_logic'; - -interface Props { - type: LogRetentionOptions; - position?: 'top' | 'right' | 'bottom' | 'left'; -} -export const LogRetentionTooltip: React.FC = ({ type, position = 'bottom' }) => { - const { fetchLogRetention } = useActions(LogRetentionLogic); - const { logRetention } = useValues(LogRetentionLogic); - const { - myRole: { canManageLogSettings }, - } = useValues(AppLogic); - - const hasLogRetention = logRetention !== null; - - useEffect(() => { - if (!hasLogRetention && canManageLogSettings) fetchLogRetention(); - }, []); - - return hasLogRetention ? ( - } - /> - ) : null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/index.ts deleted file mode 100644 index aac4b053dc5c6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 { LogRetentionLogic } from './log_retention_logic'; -export * from './types'; -export * from './messaging'; -export * from './components'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts deleted file mode 100644 index f281d82b900f2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { LogRetentionOptions } from './types'; - -import { LogRetentionLogic } from '.'; - -describe('LogRetentionLogic', () => { - const { mount } = new LogicMounter(LogRetentionLogic); - const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; - - const TYPICAL_SERVER_LOG_RETENTION = { - analytics: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - api: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - audit: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - crawler: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - }; - - const TYPICAL_CLIENT_LOG_RETENTION = { - analytics: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - api: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - audit: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - crawler: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - }; - - const DEFAULT_VALUES = { - logRetention: null, - openedModal: null, - isLogRetentionUpdating: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(LogRetentionLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('setOpenedModal', () => { - describe('openedModal', () => { - it('should be set to the provided value', () => { - mount(); - - LogRetentionLogic.actions.setOpenedModal(LogRetentionOptions.Analytics); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - openedModal: LogRetentionOptions.Analytics, - }); - }); - }); - }); - - describe('closeModals', () => { - describe('openedModal', () => { - it('resets openedModal to null', () => { - mount({ - openedModal: 'analytics', - }); - - LogRetentionLogic.actions.closeModals(); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - openedModal: null, - }); - }); - }); - - describe('isLogRetentionUpdating', () => { - it('resets isLogRetentionUpdating to false', () => { - mount({ - isLogRetentionUpdating: true, - }); - - LogRetentionLogic.actions.closeModals(); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLogRetentionUpdating: false, - }); - }); - }); - }); - - describe('clearLogRetentionUpdating', () => { - describe('isLogRetentionUpdating', () => { - it('resets isLogRetentionUpdating to false', () => { - mount({ - isLogRetentionUpdating: true, - }); - - LogRetentionLogic.actions.clearLogRetentionUpdating(); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLogRetentionUpdating: false, - }); - }); - }); - }); - - describe('updateLogRetention', () => { - describe('logRetention', () => { - it('updates the logRetention values that are passed', () => { - mount({ - logRetention: {}, - }); - - LogRetentionLogic.actions.updateLogRetention({ - api: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - analytics: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - audit: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - crawler: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - }); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - logRetention: { - api: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - analytics: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - audit: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - crawler: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - }, - }); - }); - }); - }); - }); - - describe('listeners', () => { - describe('saveLogRetention', () => { - beforeEach(() => { - mount(); - jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); - }); - - describe('openedModal', () => { - it('should be reset to null', () => { - mount({ - openedModal: LogRetentionOptions.Analytics, - }); - - LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - openedModal: null, - }); - }); - }); - - it('will call an API endpoint and update log retention', async () => { - jest.spyOn(LogRetentionLogic.actions, 'updateLogRetention'); - http.put.mockReturnValue(Promise.resolve(TYPICAL_SERVER_LOG_RETENTION)); - - LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); - - expect(http.put).toHaveBeenCalledWith('/internal/app_search/log_settings', { - body: JSON.stringify({ - analytics: { - enabled: true, - }, - }), - }); - - await nextTick(); - expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( - TYPICAL_CLIENT_LOG_RETENTION - ); - - expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); - }); - - it('handles errors', async () => { - http.put.mockReturnValue(Promise.reject('An error occured')); - - LogRetentionLogic.actions.saveLogRetention(LogRetentionOptions.Analytics, true); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); - expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); - }); - }); - - describe('toggleLogRetention', () => { - describe('isLogRetentionUpdating', () => { - it('sets isLogRetentionUpdating to true', () => { - mount({ - isLogRetentionUpdating: false, - }); - - LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLogRetentionUpdating: true, - }); - }); - }); - - it('will call setOpenedModal if already enabled', () => { - mount({ - logRetention: { - [LogRetentionOptions.Analytics]: { - enabled: true, - }, - }, - }); - jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - - LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); - - expect(LogRetentionLogic.actions.setOpenedModal).toHaveBeenCalledWith( - LogRetentionOptions.Analytics - ); - }); - - it('will call saveLogRetention if NOT already enabled', () => { - mount({ - logRetention: { - [LogRetentionOptions.Analytics]: { - enabled: false, - }, - }, - }); - jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); - - LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.Analytics); - - expect(LogRetentionLogic.actions.saveLogRetention).toHaveBeenCalledWith( - LogRetentionOptions.Analytics, - true - ); - }); - - it('will do nothing if logRetention option is not yet set', () => { - mount({ - logRetention: {}, - }); - jest.spyOn(LogRetentionLogic.actions, 'saveLogRetention'); - jest.spyOn(LogRetentionLogic.actions, 'setOpenedModal'); - - LogRetentionLogic.actions.toggleLogRetention(LogRetentionOptions.API); - - expect(LogRetentionLogic.actions.saveLogRetention).not.toHaveBeenCalled(); - expect(LogRetentionLogic.actions.setOpenedModal).not.toHaveBeenCalled(); - }); - }); - - describe('fetchLogRetention', () => { - beforeAll(() => jest.useFakeTimers({ legacyFakeTimers: true })); - afterAll(() => jest.useRealTimers()); - - describe('isLogRetentionUpdating', () => { - it('sets isLogRetentionUpdating to true', () => { - mount({ - isLogRetentionUpdating: false, - }); - - LogRetentionLogic.actions.fetchLogRetention(); - - expect(LogRetentionLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLogRetentionUpdating: true, - }); - }); - }); - - it('will call an API endpoint and update log retention', async () => { - mount(); - jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); - jest - .spyOn(LogRetentionLogic.actions, 'updateLogRetention') - .mockImplementationOnce(() => {}); - - http.get.mockReturnValue(Promise.resolve(TYPICAL_SERVER_LOG_RETENTION)); - - LogRetentionLogic.actions.fetchLogRetention(); - jest.runAllTimers(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/log_settings'); - expect(LogRetentionLogic.actions.updateLogRetention).toHaveBeenCalledWith( - TYPICAL_CLIENT_LOG_RETENTION - ); - expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); - }); - - it('handles errors', async () => { - mount(); - jest.spyOn(LogRetentionLogic.actions, 'clearLogRetentionUpdating'); - http.get.mockReturnValue(Promise.reject('An error occured')); - - LogRetentionLogic.actions.fetchLogRetention(); - jest.runAllTimers(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); - expect(LogRetentionLogic.actions.clearLogRetentionUpdating).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts deleted file mode 100644 index a1476793a11e4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; - -import { LogRetentionOptions, LogRetention, LogRetentionServer } from './types'; -import { convertLogRetentionFromServerToClient } from './utils/convert_log_retention'; - -interface LogRetentionActions { - clearLogRetentionUpdating(): void; - closeModals(): void; - fetchLogRetention(): void; - saveLogRetention( - option: LogRetentionOptions, - enabled: boolean - ): { option: LogRetentionOptions; enabled: boolean }; - setOpenedModal(option: LogRetentionOptions): { option: LogRetentionOptions }; - toggleLogRetention(option: LogRetentionOptions): { option: LogRetentionOptions }; - updateLogRetention(logRetention: LogRetention): { logRetention: LogRetention }; -} - -interface LogRetentionValues { - logRetention: LogRetention | null; - isLogRetentionUpdating: boolean; - openedModal: LogRetentionOptions | null; -} - -export const LogRetentionLogic = kea>({ - path: ['enterprise_search', 'app_search', 'log_retention_logic'], - actions: () => ({ - clearLogRetentionUpdating: true, - closeModals: true, - fetchLogRetention: true, - saveLogRetention: (option, enabled) => ({ enabled, option }), - setOpenedModal: (option) => ({ option }), - toggleLogRetention: (option) => ({ option }), - updateLogRetention: (logRetention) => ({ logRetention }), - }), - reducers: () => ({ - logRetention: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - updateLogRetention: (_, { logRetention }) => logRetention, - }, - ], - isLogRetentionUpdating: [ - false, - { - clearLogRetentionUpdating: () => false, - closeModals: () => false, - fetchLogRetention: () => true, - toggleLogRetention: () => true, - }, - ], - openedModal: [ - null, - { - closeModals: () => null, - saveLogRetention: () => null, - // @ts-expect-error upgrade typescript v5.1.6 - setOpenedModal: (_, { option }) => option, - }, - ], - }), - listeners: ({ actions, values }) => ({ - fetchLogRetention: async (_, breakpoint) => { - await breakpoint(100); // Prevents duplicate calls to the API (e.g., when a tooltip & callout are on the same page) - - try { - const { http } = HttpLogic.values; - const response = await http.get('/internal/app_search/log_settings'); - - actions.updateLogRetention( - convertLogRetentionFromServerToClient(response as LogRetentionServer) - ); - } catch (e) { - flashAPIErrors(e); - } finally { - actions.clearLogRetentionUpdating(); - } - }, - saveLogRetention: async ({ enabled, option }) => { - const updateData = { [option]: { enabled } }; - - try { - const { http } = HttpLogic.values; - const response = await http.put('/internal/app_search/log_settings', { - body: JSON.stringify(updateData), - }); - actions.updateLogRetention( - convertLogRetentionFromServerToClient(response as LogRetentionServer) - ); - } catch (e) { - flashAPIErrors(e); - } finally { - actions.clearLogRetentionUpdating(); - } - }, - toggleLogRetention: ({ option }) => { - const logRetention = values.logRetention?.[option]; - - // If the user has found a way to call this before we've retrieved - // log retention settings from the server, short circuit this and return early - if (!logRetention) { - return; - } - - const optionIsAlreadyEnabled = logRetention.enabled; - if (optionIsAlreadyEnabled) { - actions.setOpenedModal(option); - } else { - actions.saveLogRetention(option, true); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/constants.tsx deleted file mode 100644 index deef693166711..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/constants.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { FormattedDate, FormattedMessage } from '@kbn/i18n-react'; - -import { LogRetentionOptions, LogRetentionSettings, LogRetentionPolicy } from '../types'; - -export const renderLogRetentionDate = (dateString: string) => ( - -); - -const CAPITALIZATION_MAP = { - [LogRetentionOptions.Analytics]: { - capitalized: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.capitalized', - { defaultMessage: 'Analytics' } - ), - lowercase: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.analytics.title.lowercase', - { defaultMessage: 'analytics' } - ), - }, - [LogRetentionOptions.API]: { - capitalized: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.api.title.capitalized', - { defaultMessage: 'API' } - ), - lowercase: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.api.title.lowercase', - { defaultMessage: 'API' } - ), - }, - [LogRetentionOptions.Audit]: { - capitalized: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.capitalized', - { defaultMessage: 'Audit' } - ), - lowercase: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.audit.title.lowercase', - { defaultMessage: 'audit' } - ), - }, - [LogRetentionOptions.Crawler]: { - capitalized: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.capitalized', - { defaultMessage: 'Web crawler' } - ), - lowercase: i18n.translate( - 'xpack.enterpriseSearch.appSearch.logRetention.type.crawler.title.lowercase', - { defaultMessage: 'web crawler' } - ), - }, -}; - -interface Props { - type: LogRetentionOptions; - disabledAt?: LogRetentionSettings['disabledAt']; - minAgeDays?: LogRetentionPolicy['minAgeDays']; -} - -export const NoLogging: React.FC = ({ type, disabledAt }) => { - return ( - <> - {' '} - {disabledAt ? ( - - ) : ( - - )} - - ); -}; - -export const CustomPolicy: React.FC = ({ type }) => ( - -); - -export const DefaultPolicy: React.FC = ({ type, minAgeDays }) => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/index.ts deleted file mode 100644 index af76a710b6288..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { LogRetentionMessage } from './log_retention_message'; -export { renderLogRetentionDate } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/log_retention_message.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/log_retention_message.test.tsx deleted file mode 100644 index 31c8b4d492752..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/log_retention_message.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { LogRetentionOptions } from '../types'; - -import { LogRetentionMessage } from '.'; - -describe('LogRetentionMessage', () => { - const analytics = LogRetentionOptions.Analytics; - const api = LogRetentionOptions.API; - - const setLogRetention = (logRetention: object) => { - const logRetentionSettings = { - disabledAt: null, - enabled: true, - retentionPolicy: null, - ...logRetention, - }; - - setMockValues({ - logRetention: { - [LogRetentionOptions.API]: logRetentionSettings, - [LogRetentionOptions.Analytics]: logRetentionSettings, - }, - }); - }; - - it('does not render if ILM is unavailable', () => { - setMockValues({ logRetention: null }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('does not render if log retention settings are empty', () => { - setMockValues({ logRetention: { api: null } }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - describe('when logs are enabled', () => { - describe("and they're using the default policy", () => { - describe('a retention policy message renders', () => { - beforeEach(() => { - setLogRetention({ - enabled: true, - retentionPolicy: { - isDefault: true, - minAgeDays: 7, - }, - }); - }); - - it('for analytics', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual( - 'Your analytics logs are being stored for at least 7 days.' - ); - }); - - it('for api', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual('Your API logs are being stored for at least 7 days.'); - }); - }); - }); - - describe('and there is a custom policy', () => { - describe('a retention policy message renders', () => { - beforeEach(() => { - setLogRetention({ - enabled: true, - retentionPolicy: { - isDefault: false, - minAgeDays: 7, - }, - }); - }); - - it('for analytics', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual('You have a custom analytics log retention policy.'); - }); - - it('for api', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual('You have a custom API log retention policy.'); - }); - }); - }); - }); - - describe('when logs are disabled', () => { - describe('and there is no disabledAt date', () => { - describe('a no logging message renders', () => { - beforeEach(() => { - setLogRetention({ - enabled: false, - disabledAt: null, - }); - }); - - it('for api', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual( - 'API logging has been disabled for all engines. There are no API logs collected.' - ); - }); - - it('for analytics', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual( - 'Analytics logging has been disabled for all engines. There are no analytics logs collected.' - ); - }); - }); - }); - - describe('and there is a disabledAt date', () => { - describe('a no logging message renders with a date', () => { - beforeEach(() => { - setLogRetention({ - enabled: false, - disabledAt: 'Thu, 05 Nov 2020 18:57:28 +0000', - }); - }); - - it('for analytics', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual( - 'Analytics logging has been disabled for all engines. The last date analytics logs were collected was November 5, 2020.' - ); - }); - - it('for api', () => { - const wrapper = mountWithIntl(); - expect(wrapper.text()).toEqual( - 'API logging has been disabled for all engines. The last date API logs were collected was November 5, 2020.' - ); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/log_retention_message.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/log_retention_message.tsx deleted file mode 100644 index c461de72edb88..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/messaging/log_retention_message.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { LogRetentionLogic } from '../log_retention_logic'; -import { LogRetentionOptions } from '../types'; - -import { NoLogging, CustomPolicy, DefaultPolicy } from './constants'; - -interface Props { - type: LogRetentionOptions; -} -export const LogRetentionMessage: React.FC = ({ type }) => { - const { logRetention } = useValues(LogRetentionLogic); - if (!logRetention) return null; - - const logRetentionSettings = logRetention[type]; - if (!logRetentionSettings) return null; - - if (!logRetentionSettings.enabled) { - return ; - } - if (!logRetentionSettings.retentionPolicy?.isDefault) { - return ; - } else { - return ( - - ); - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/types.ts deleted file mode 100644 index 79d555b99a803..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 enum LogRetentionOptions { - Analytics = 'analytics', - API = 'api', - Audit = 'audit', - Crawler = 'crawler', -} - -export interface LogRetention { - [LogRetentionOptions.Analytics]: LogRetentionSettings; - [LogRetentionOptions.API]: LogRetentionSettings; - [LogRetentionOptions.Audit]: LogRetentionSettings; - [LogRetentionOptions.Crawler]: LogRetentionSettings; -} - -export interface LogRetentionPolicy { - isDefault: boolean; - minAgeDays: number | null; -} - -export interface LogRetentionSettings { - disabledAt?: string | null; - enabled?: boolean; - retentionPolicy?: LogRetentionPolicy | null; -} - -export interface LogRetentionServer { - [LogRetentionOptions.Analytics]: LogRetentionServerSettings; - [LogRetentionOptions.API]: LogRetentionServerSettings; - [LogRetentionOptions.Audit]: LogRetentionServerSettings; - [LogRetentionOptions.Crawler]: LogRetentionServerSettings; -} - -export interface LogRetentionServerPolicy { - is_default: boolean; - min_age_days: number | null; -} - -export interface LogRetentionServerSettings { - disabled_at: string | null; - enabled: boolean; - retention_policy: LogRetentionServerPolicy | null; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/utils/convert_log_retention.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/utils/convert_log_retention.test.ts deleted file mode 100644 index a99c3d6888d3f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/utils/convert_log_retention.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 { convertLogRetentionFromServerToClient } from './convert_log_retention'; - -describe('convertLogRetentionFromServerToClient', () => { - it('converts log retention from server to client', () => { - expect( - convertLogRetentionFromServerToClient({ - analytics: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - api: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - audit: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - crawler: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: 180 }, - }, - }) - ).toEqual({ - analytics: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - api: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - audit: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - crawler: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - }); - }); - - it('handles null retention policies and null min_age_days', () => { - expect( - convertLogRetentionFromServerToClient({ - analytics: { - disabled_at: null, - enabled: true, - retention_policy: null, - }, - api: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: null }, - }, - audit: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: null }, - }, - crawler: { - disabled_at: null, - enabled: true, - retention_policy: { is_default: true, min_age_days: null }, - }, - }) - ).toEqual({ - analytics: { - disabledAt: null, - enabled: true, - retentionPolicy: null, - }, - api: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: null }, - }, - audit: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: null }, - }, - crawler: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: null }, - }, - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/utils/convert_log_retention.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/utils/convert_log_retention.ts deleted file mode 100644 index 50af7ea54e2e2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/utils/convert_log_retention.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { - LogRetentionOptions, - LogRetention, - LogRetentionPolicy, - LogRetentionServer, - LogRetentionServerPolicy, - LogRetentionServerSettings, - LogRetentionSettings, -} from '../types'; - -export const convertLogRetentionFromServerToClient = ( - logRetention: LogRetentionServer -): LogRetention => ({ - [LogRetentionOptions.Analytics]: convertLogRetentionSettingsFromServerToClient( - logRetention[LogRetentionOptions.Analytics] - ), - [LogRetentionOptions.API]: convertLogRetentionSettingsFromServerToClient( - logRetention[LogRetentionOptions.API] - ), - [LogRetentionOptions.Audit]: convertLogRetentionSettingsFromServerToClient( - logRetention[LogRetentionOptions.Audit] - ), - [LogRetentionOptions.Crawler]: convertLogRetentionSettingsFromServerToClient( - logRetention[LogRetentionOptions.Crawler] - ), -}); - -const convertLogRetentionSettingsFromServerToClient = ({ - disabled_at: disabledAt, - enabled, - retention_policy: retentionPolicy, -}: LogRetentionServerSettings): LogRetentionSettings => ({ - disabledAt, - enabled, - retentionPolicy: - retentionPolicy === null ? null : convertLogRetentionPolicyFromServerToClient(retentionPolicy), -}); - -const convertLogRetentionPolicyFromServerToClient = ({ - min_age_days: minAgeDays, - is_default: isDefault, -}: LogRetentionServerPolicy): LogRetentionPolicy => ({ - isDefault, - minAgeDays, -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx deleted file mode 100644 index 8b32ffe77e701..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 { EuiLink } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../shared/doc_links'; - -export const DEFAULT_LANGUAGE = 'Universal'; - -export const META_ENGINE_CREATION_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.title', - { - defaultMessage: 'Create a meta engine', - } -); - -export const META_ENGINE_CREATION_FORM_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.title', - { - defaultMessage: 'Name your meta engine', - } -); - -export const META_ENGINE_CREATION_FORM_SUBMIT_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.submitButton.buttonLabel', - { - defaultMessage: 'Create meta engine', - } -); - -export const META_ENGINE_CREATION_FORM_META_ENGINE_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.metaEngineDescription', - { - defaultMessage: - 'Meta engines allow you to combine multiple engines into one searchable engine.', - } -); - -export const META_ENGINE_CREATION_FORM_DOCUMENTATION_LINK = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.documentationLink', - { - defaultMessage: 'Read the documentation', - } -); - -export const META_ENGINE_CREATION_FORM_DOCUMENTATION_DESCRIPTION = ( - - {META_ENGINE_CREATION_FORM_DOCUMENTATION_LINK} - - ), - }} - /> -); - -export const META_ENGINE_CREATION_FORM_ENGINE_NAME_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.label', - { - defaultMessage: 'Meta engine name', - } -); - -export const ALLOWED_CHARS_NOTE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.allowedCharactersHelpText', - { - defaultMessage: 'Meta engine names can only contain lowercase letters, numbers, and hyphens', - } -); - -export const SANITIZED_NAME_NOTE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.sanitizedNameHelpText', - { - defaultMessage: 'Your meta engine will be named', - } -); - -export const META_ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.engineName.placeholder', - { - defaultMessage: 'i.e., my-meta-engine', - } -); - -export const META_ENGINE_CREATION_FORM_ENGINE_SOURCE_ENGINES_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.label', - { - defaultMessage: 'Add source engines to this meta engine', - } -); - -export const META_ENGINE_CREATION_FORM_MAX_SOURCE_ENGINES_WARNING_TITLE = ( - maxEnginesPerMetaEngine: number -) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.metaEngineCreation.form.sourceEngines.maxSourceEnginesWarningTitle', - { - defaultMessage: 'Meta engines have a limit of {maxEnginesPerMetaEngine} source engines', - values: { maxEnginesPerMetaEngine }, - } - ); - -export const META_ENGINE_CREATION_SUCCESS_MESSAGE = (name: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.metaEngineCreation.successMessage', { - defaultMessage: "Meta engine ''{name}'' was created", - values: { name }, - }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/index.ts deleted file mode 100644 index 2d33322a84fc6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { MetaEngineCreation } from './meta_engine_creation'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.test.tsx deleted file mode 100644 index 7697fc1ca9b19..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.test.tsx +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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 '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCallOut } from '@elastic/eui'; - -import { MetaEngineCreation } from '.'; - -const DEFAULT_VALUES = { - // MetaEngineLogic - isLoading: false, - name: 'test-meta-engine', - rawName: 'test-meta-engine', - indexedEngineNames: [], - selectedIndexedEngineNames: ['one'], - // AppLogic - configuredLimits: { engine: { maxEnginesPerMetaEngine: 10 } }, -}; - -const MOCK_ACTIONS = { - setRawName: jest.fn(), - setSelectedIndexedEngineNames: jest.fn(), - fetchIndexedEngineNames: jest.fn(), - submitEngine: jest.fn(), -}; - -describe('MetaEngineCreation', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('renders and calls fetchIndexedEngineNames', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="MetaEngineCreation"]')).toHaveLength(1); - expect(MOCK_ACTIONS.fetchIndexedEngineNames).toHaveBeenCalledTimes(1); - }); - - describe('MetaEngineCreationNameInput', () => { - it('uses rawName as its value', () => { - const wrapper = shallow(); - expect( - wrapper - .find('[data-test-subj="MetaEngineCreationNameInput"]') - .render() - .find('input') // as far as I can tell I can't include this input in the .find() two lines above - .attr('value') - ).toEqual('test-meta-engine'); - }); - - it('EngineCreationForm calls submitEngine on form submit', () => { - const wrapper = shallow(); - const simulatedEvent = { - preventDefault: jest.fn(), - }; - wrapper.find('[data-test-subj="MetaEngineCreationForm"]').simulate('submit', simulatedEvent); - - expect(MOCK_ACTIONS.submitEngine).toHaveBeenCalledTimes(1); - }); - - it('MetaEngineCreationNameInput calls setRawName on change', () => { - const wrapper = shallow(); - const simulatedEvent = { - currentTarget: { value: 'new-raw-name' }, - }; - wrapper - .find('[data-test-subj="MetaEngineCreationNameInput"]') - .simulate('change', simulatedEvent); - expect(MOCK_ACTIONS.setRawName).toHaveBeenCalledWith('new-raw-name'); - }); - }); - - describe('EngineCreationNameFormRow', () => { - it('renders sanitized name helptext when the raw name is being sanitized', () => { - setMockValues({ - ...DEFAULT_VALUES, - name: 'name-with-special-characters', - rawName: 'Name__With#$&*%Special--Characters', - }); - - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="MetaEngineCreationNameFormRow"]').dive(); - - expect(formRow.contains('Your meta engine will be named')).toBeTruthy(); - }); - - it('renders allowed character helptext when rawName and sanitizedName match', () => { - setMockValues({ - ...DEFAULT_VALUES, - name: 'name-without-special-characters', - rawName: 'name-without-special-characters', - }); - - const wrapper = shallow(); - const formRow = wrapper.find('[data-test-subj="MetaEngineCreationNameFormRow"]').dive(); - - expect( - formRow.contains( - 'Meta engine names can only contain lowercase letters, numbers, and hyphens' - ) - ).toBeTruthy(); - }); - }); - - it('MetaEngineCreationSourceEnginesInput calls calls setSelectedIndexedEngines on change', () => { - const wrapper = shallow(); - - wrapper - .find('[data-test-subj="MetaEngineCreationSourceEnginesInput"]') - .simulate('change', [{ label: 'foo', value: 'foo' }]); - - expect(MOCK_ACTIONS.setSelectedIndexedEngineNames).toHaveBeenCalledWith(['foo']); - }); - - it('renders a warning callout when user has selected too many engines', () => { - setMockValues({ - ...DEFAULT_VALUES, - ...{ - selectedIndexedEngineNames: ['one', 'two', 'three'], - configuredLimits: { engine: { maxEnginesPerMetaEngine: 2 } }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut).prop('title')).toContain('Meta engines have a limit of'); - }); - - describe('NewMetaEngineSubmitButton', () => { - it('is enabled for a valid submission', () => { - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewMetaEngineSubmitButton"]'); - - expect(submitButton.prop('disabled')).toEqual(false); - }); - - it('is disabled when name is empty', () => { - setMockValues({ - ...DEFAULT_VALUES, - ...{ - name: '', - rawName: '', - }, - }); - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewMetaEngineSubmitButton"]'); - - expect(submitButton.prop('disabled')).toEqual(true); - }); - - it('is disabled when user has selected no engines', () => { - setMockValues({ - ...DEFAULT_VALUES, - ...{ - selectedIndexedEngineNames: [], - }, - }); - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewMetaEngineSubmitButton"]'); - - expect(submitButton.prop('disabled')).toEqual(true); - }); - - it('is disabled when user has selected too many engines', () => { - setMockValues({ - ...DEFAULT_VALUES, - ...{ - selectedIndexedEngineNames: ['one', 'two', 'three'], - configuredLimits: { engine: { maxEnginesPerMetaEngine: 2 } }, - }, - }); - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewMetaEngineSubmitButton"]'); - - expect(submitButton.prop('disabled')).toEqual(true); - }); - - it('passes isLoading state', () => { - setMockValues({ ...DEFAULT_VALUES, isLoading: true }); - const wrapper = shallow(); - const submitButton = wrapper.find('[data-test-subj="NewMetaEngineSubmitButton"]'); - - expect(submitButton.prop('isLoading')).toEqual(true); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx deleted file mode 100644 index ed1ac6fdce0b6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation.tsx +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiCallOut, - EuiComboBox, - EuiComboBoxOptionOption, - EuiForm, - EuiFlexGroup, - EuiFormRow, - EuiFlexItem, - EuiFieldText, - EuiPanel, - EuiSpacer, - EuiTitle, - EuiButton, -} from '@elastic/eui'; - -import { AppLogic } from '../../app_logic'; -import { ENGINES_TITLE } from '../engines'; -import { AppSearchPageTemplate } from '../layout'; - -import { - ALLOWED_CHARS_NOTE, - META_ENGINE_CREATION_FORM_DOCUMENTATION_DESCRIPTION, - META_ENGINE_CREATION_FORM_ENGINE_NAME_LABEL, - META_ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER, - META_ENGINE_CREATION_FORM_ENGINE_SOURCE_ENGINES_LABEL, - META_ENGINE_CREATION_FORM_MAX_SOURCE_ENGINES_WARNING_TITLE, - META_ENGINE_CREATION_FORM_META_ENGINE_DESCRIPTION, - META_ENGINE_CREATION_FORM_SUBMIT_BUTTON_LABEL, - META_ENGINE_CREATION_FORM_TITLE, - META_ENGINE_CREATION_TITLE, - SANITIZED_NAME_NOTE, -} from './constants'; -import { MetaEngineCreationLogic } from './meta_engine_creation_logic'; - -const engineNameToComboBoxOption = (engineName: string): EuiComboBoxOptionOption => ({ - label: engineName, -}); - -const comboBoxOptionToEngineName = (option: EuiComboBoxOptionOption): string => - option.label; - -export const MetaEngineCreation: React.FC = () => { - const { - configuredLimits: { - engine: { maxEnginesPerMetaEngine }, - }, - } = useValues(AppLogic); - - const { fetchIndexedEngineNames, setRawName, setSelectedIndexedEngineNames, submitEngine } = - useActions(MetaEngineCreationLogic); - - const { rawName, name, indexedEngineNames, selectedIndexedEngineNames, isLoading } = - useValues(MetaEngineCreationLogic); - - useEffect(() => { - fetchIndexedEngineNames(); - }, []); - - return ( - - {META_ENGINE_CREATION_FORM_META_ENGINE_DESCRIPTION} -
    - {META_ENGINE_CREATION_FORM_DOCUMENTATION_DESCRIPTION} - - ), - }} - data-test-subj="MetaEngineCreation" - > - - { - e.preventDefault(); - submitEngine(); - }} - > - -

    {META_ENGINE_CREATION_FORM_TITLE}

    -
    - - - - 0 && rawName !== name ? ( - <> - {SANITIZED_NAME_NOTE} {name} - - ) : ( - ALLOWED_CHARS_NOTE - ) - } - fullWidth - > - setRawName(event.currentTarget.value)} - fullWidth - data-test-subj="MetaEngineCreationNameInput" - placeholder={META_ENGINE_CREATION_FORM_ENGINE_NAME_PLACEHOLDER} - autoFocus - /> - - - - - - { - setSelectedIndexedEngineNames(options.map(comboBoxOptionToEngineName)); - }} - /> - - {selectedIndexedEngineNames.length > maxEnginesPerMetaEngine && ( - <> - - - - )} - - maxEnginesPerMetaEngine - } - isLoading={isLoading} - type="submit" - data-test-subj="NewMetaEngineSubmitButton" - color="success" - fill - > - {META_ENGINE_CREATION_FORM_SUBMIT_BUTTON_LABEL} - -
    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts deleted file mode 100644 index 2c5a92b06d64b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, - mockKibanaValues, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { MetaEngineCreationLogic } from './meta_engine_creation_logic'; - -describe('MetaEngineCreationLogic', () => { - const { mount } = new LogicMounter(MetaEngineCreationLogic); - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - isLoading: false, - indexedEngineNames: [], - name: '', - rawName: '', - selectedIndexedEngineNames: [], - }; - - it('has expected default values', () => { - mount(); - expect(MetaEngineCreationLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('setRawName', () => { - beforeAll(() => { - jest.clearAllMocks(); - mount(); - MetaEngineCreationLogic.actions.setRawName('Name__With#$&*%Special--Characters'); - }); - - it('should set rawName to provided value', () => { - expect(MetaEngineCreationLogic.values.rawName).toEqual( - 'Name__With#$&*%Special--Characters' - ); - }); - - it('should set name to a sanitized value', () => { - expect(MetaEngineCreationLogic.values.name).toEqual('name-with-special-characters'); - }); - }); - - describe('setIndexedEngineNames', () => { - it('should set indexedEngineNames to provided value', () => { - mount(); - MetaEngineCreationLogic.actions.setIndexedEngineNames(['first', 'middle', 'last']); - expect(MetaEngineCreationLogic.values.indexedEngineNames).toEqual([ - 'first', - 'middle', - 'last', - ]); - }); - }); - - describe('setSelectedIndexedEngineNames', () => { - it('should set selectedIndexedEngineNames to provided value', () => { - mount(); - MetaEngineCreationLogic.actions.setSelectedIndexedEngineNames(['one', 'two', 'three']); - expect(MetaEngineCreationLogic.values.selectedIndexedEngineNames).toEqual([ - 'one', - 'two', - 'three', - ]); - }); - }); - - describe('submitEngine', () => { - it('sets isLoading to true', () => { - mount({ isLoading: false }); - MetaEngineCreationLogic.actions.submitEngine(); - expect(MetaEngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLoading: true, - }); - }); - }); - - describe('onSubmitError', () => { - it('resets isLoading to false', () => { - mount({ isLoading: true }); - MetaEngineCreationLogic.actions.onSubmitError(); - expect(MetaEngineCreationLogic.values).toEqual({ - ...DEFAULT_VALUES, - isLoading: false, - }); - }); - }); - }); - - describe('listeners', () => { - describe('fetchIndexedEngineNames', () => { - beforeEach(() => { - mount(); - jest.clearAllMocks(); - }); - - it('calls flashAPIErrors on API Error', async () => { - http.get.mockReturnValueOnce(Promise.reject()); - MetaEngineCreationLogic.actions.fetchIndexedEngineNames(); - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - }); - - it('calls onEngineCreationSuccess on valid submission', async () => { - jest.spyOn(MetaEngineCreationLogic.actions, 'setIndexedEngineNames'); - http.get.mockReturnValueOnce( - Promise.resolve({ results: [{ name: 'foo' }], meta: { page: { total_pages: 1 } } }) - ); - MetaEngineCreationLogic.actions.fetchIndexedEngineNames(); - await nextTick(); - expect(MetaEngineCreationLogic.actions.setIndexedEngineNames).toHaveBeenCalledWith(['foo']); - }); - - it('filters out elasticsearch type engines', async () => { - jest.spyOn(MetaEngineCreationLogic.actions, 'setIndexedEngineNames'); - http.get.mockReturnValueOnce( - Promise.resolve({ - results: [ - { name: 'foo', type: 'default' }, - { name: 'elasticsearch-engine', type: 'elasticsearch' }, - ], - meta: { page: { total_pages: 1 } }, - }) - ); - MetaEngineCreationLogic.actions.fetchIndexedEngineNames(); - await nextTick(); - expect(MetaEngineCreationLogic.actions.setIndexedEngineNames).toHaveBeenCalledWith([ - 'foo', - 'elasticsearch-engine', - ]); - }); - - it('if there are remaining pages it should call fetchIndexedEngineNames recursively with an incremented page', async () => { - jest.spyOn(MetaEngineCreationLogic.actions, 'fetchIndexedEngineNames'); - http.get.mockReturnValueOnce( - Promise.resolve({ results: [{ name: 'foo' }], meta: { page: { total_pages: 2 } } }) - ); - MetaEngineCreationLogic.actions.fetchIndexedEngineNames(); - await nextTick(); - expect(MetaEngineCreationLogic.actions.fetchIndexedEngineNames).toHaveBeenCalledWith(2); - }); - - it('if there are no remaining pages it should end without calling recursively', async () => { - jest.spyOn(MetaEngineCreationLogic.actions, 'fetchIndexedEngineNames'); - http.get.mockReturnValueOnce( - Promise.resolve({ results: [{ name: 'foo' }], meta: { page: { total_pages: 1 } } }) - ); - MetaEngineCreationLogic.actions.fetchIndexedEngineNames(); - await nextTick(); - expect(MetaEngineCreationLogic.actions.fetchIndexedEngineNames).toHaveBeenCalledTimes(1); // it's one time cause we called it two lines above - }); - }); - - describe('onEngineCreationSuccess', () => { - beforeAll(() => { - jest.clearAllMocks(); - mount({ language: 'English', rawName: 'test' }); - MetaEngineCreationLogic.actions.onEngineCreationSuccess(); - }); - - it('should show a success message', () => { - expect(flashSuccessToast).toHaveBeenCalledWith("Meta engine 'test' was created"); - }); - - it('should navigate the user to the engine page', () => { - expect(navigateToUrl).toHaveBeenCalledWith('/engines/test'); - }); - }); - - describe('submitEngine', () => { - beforeAll(() => { - jest.clearAllMocks(); - mount({ rawName: 'test', selectedIndexedEngineNames: ['foo'] }); - }); - - it('POSTS to /internal/app_search/engines', () => { - const body = JSON.stringify({ - name: 'test', - type: 'meta', - source_engines: ['foo'], - }); - MetaEngineCreationLogic.actions.submitEngine(); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines', { body }); - }); - - it('calls onEngineCreationSuccess on valid submission', async () => { - jest.spyOn(MetaEngineCreationLogic.actions, 'onEngineCreationSuccess'); - http.post.mockReturnValueOnce(Promise.resolve({})); - MetaEngineCreationLogic.actions.submitEngine(); - await nextTick(); - expect(MetaEngineCreationLogic.actions.onEngineCreationSuccess).toHaveBeenCalledTimes(1); - }); - - it('calls flashAPIErrors on API Error', async () => { - jest.spyOn(MetaEngineCreationLogic.actions, 'setIndexedEngineNames'); - http.post.mockReturnValueOnce(Promise.reject()); - MetaEngineCreationLogic.actions.submitEngine(); - await nextTick(); - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - expect(MetaEngineCreationLogic.actions.setIndexedEngineNames).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.ts deleted file mode 100644 index f30c8f99f9b86..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 { generatePath } from 'react-router-dom'; - -import { kea, MakeLogicType } from 'kea'; - -import { Meta } from '../../../../../common/types'; -import { DEFAULT_META } from '../../../shared/constants'; -import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; -import { ENGINE_PATH } from '../../routes'; -import { formatApiName } from '../../utils/format_api_name'; -import { EngineDetails } from '../engine/types'; - -import { META_ENGINE_CREATION_SUCCESS_MESSAGE } from './constants'; - -interface MetaEngineCreationValues { - indexedEngineNames: string[]; - name: string; - rawName: string; - selectedIndexedEngineNames: string[]; - isLoading: boolean; -} - -interface MetaEngineCreationActions { - fetchIndexedEngineNames(page?: number): { page: number }; - onEngineCreationSuccess(): void; - setIndexedEngineNames(indexedEngineNames: MetaEngineCreationValues['indexedEngineNames']): { - indexedEngineNames: MetaEngineCreationValues['indexedEngineNames']; - }; - setRawName(rawName: string): { rawName: string }; - setSelectedIndexedEngineNames( - selectedIndexedEngineNames: MetaEngineCreationValues['selectedIndexedEngineNames'] - ): { selectedIndexedEngineNames: MetaEngineCreationValues['selectedIndexedEngineNames'] }; - submitEngine(): void; - onSubmitError(): void; -} - -export const MetaEngineCreationLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'meta_engine_creation_logic'], - actions: { - fetchIndexedEngineNames: (page = DEFAULT_META.page.current) => ({ page }), - onEngineCreationSuccess: true, - setIndexedEngineNames: (indexedEngineNames) => ({ indexedEngineNames }), - setRawName: (rawName) => ({ rawName }), - setSelectedIndexedEngineNames: (selectedIndexedEngineNames) => ({ selectedIndexedEngineNames }), - submitEngine: true, - onSubmitError: true, - }, - reducers: { - isLoading: [ - false, - { - submitEngine: () => true, - onSubmitError: () => false, - }, - ], - indexedEngineNames: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setIndexedEngineNames: (_, { indexedEngineNames }) => indexedEngineNames, - }, - ], - rawName: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setRawName: (_, { rawName }) => rawName, - }, - ], - selectedIndexedEngineNames: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setSelectedIndexedEngineNames: (_, { selectedIndexedEngineNames }) => - selectedIndexedEngineNames, - }, - ], - }, - selectors: ({ selectors }) => ({ - name: [() => [selectors.rawName], (rawName: string) => formatApiName(rawName)], - }), - listeners: ({ values, actions }) => ({ - fetchIndexedEngineNames: async ({ page }) => { - const { http } = HttpLogic.values; - let response: { results: EngineDetails[]; meta: Meta } | undefined; - - try { - response = await http.get('/internal/app_search/engines', { - query: { type: 'indexed', 'page[current]': page, 'page[size]': DEFAULT_META.page.size }, - }); - } catch (e) { - flashAPIErrors(e); - } - - if (response) { - const engineNames = response.results.map((result) => result.name); - actions.setIndexedEngineNames([...values.indexedEngineNames, ...engineNames]); - - if (page < response.meta.page.total_pages) { - actions.fetchIndexedEngineNames(page + 1); - } - } - }, - onEngineCreationSuccess: () => { - const { name } = values; - const { navigateToUrl } = KibanaLogic.values; - const enginePath = generatePath(ENGINE_PATH, { engineName: name }); - - flashSuccessToast(META_ENGINE_CREATION_SUCCESS_MESSAGE(name)); - navigateToUrl(enginePath); - }, - submitEngine: async () => { - const { http } = HttpLogic.values; - const { name, selectedIndexedEngineNames } = values; - - const body = JSON.stringify({ - name, - type: 'meta', - source_engines: selectedIndexedEngineNames, - }); - - try { - await http.post('/internal/app_search/engines', { body }); - actions.onEngineCreationSuccess(); - } catch (e) { - flashAPIErrors(e); - actions.onSubmitError(); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/constants.ts deleted file mode 100644 index 1b16ec0082f85..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/constants.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const ADD_VALUE_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.multiInputRows.addValueButtonLabel', - { defaultMessage: 'Add value' } -); - -export const DELETE_ROW_VALUE_BUTTON_LABEL = (index: number) => - i18n.translate('xpack.enterpriseSearch.appSearch.multiInputRows.removeRowValueButtonLabel', { - defaultMessage: 'Remove value, row {index}', - values: { index }, - }); - -export const INPUT_ROW_PLACEHOLDER = i18n.translate( - 'xpack.enterpriseSearch.appSearch.multiInputRows.inputRowPlaceholder', - { defaultMessage: 'Enter a value' } -); - -export const INPUT_ROW_CONTAINER_ARIA_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.multiInputRows.inputRowContainerAriaLabel', - { defaultMessage: 'Multiple input rows' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/index.ts deleted file mode 100644 index 553bf23f21d30..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { MultiInputRows } from './multi_input_rows'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.scss deleted file mode 100644 index 8c256c66a8dbf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.scss +++ /dev/null @@ -1,3 +0,0 @@ -.inputRow { - margin-bottom: $euiSizeXS; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx deleted file mode 100644 index c999881fa9fed..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { EuiFieldText } from '@elastic/eui'; - -import { InputRow } from './input_row'; - -describe('InputRow', () => { - const props = { - value: 'some value', - placeholder: 'Enter a value', - autoFocus: false, - onChange: jest.fn(), - onDelete: jest.fn(), - disableDelete: false, - deleteLabel: 'Delete value', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldText)).toHaveLength(1); - expect(wrapper.find(EuiFieldText).prop('value')).toEqual('some value'); - expect(wrapper.find(EuiFieldText).prop('placeholder')).toEqual('Enter a value'); - expect(wrapper.find(EuiFieldText).prop('autoFocus')).toEqual(false); - expect(wrapper.find('[data-test-subj="deleteInputRowButton"]').prop('title')).toEqual( - 'Delete value' - ); - }); - - it('calls onChange when the input value changes', () => { - const wrapper = shallow(); - wrapper.find(EuiFieldText).simulate('change', { target: { value: 'new value' } }); - - expect(props.onChange).toHaveBeenCalledWith('new value'); - }); - - it('calls onDelete when the delete button is clicked', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="deleteInputRowButton"]').simulate('click'); - - expect(props.onDelete).toHaveBeenCalled(); - }); - - it('disables the delete button if disableDelete is passed', () => { - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="deleteInputRowButton"]'); - - expect(button.prop('isDisabled')).toEqual(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx deleted file mode 100644 index 55ed5e9d90509..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButtonIcon } from '@elastic/eui'; - -interface Props { - value: string; - placeholder: string; - autoFocus: boolean; - onChange(newValue: string): void; - onDelete(): void; - disableDelete: boolean; - deleteLabel: string; -} - -import './input_row.scss'; - -export const InputRow: React.FC = ({ - value, - placeholder, - autoFocus, - onChange, - onDelete, - disableDelete, - deleteLabel, -}) => ( - - - onChange(e.target.value)} - autoFocus={autoFocus} - /> - - - - - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx deleted file mode 100644 index a0f2cb30e33e6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { rerender } from '../../../test_helpers'; - -import { InputRow } from './input_row'; - -jest.mock('./multi_input_rows_logic', () => ({ - MultiInputRowsLogic: jest.fn(), -})); -import { MultiInputRowsLogic } from './multi_input_rows_logic'; - -import { MultiInputRows } from '.'; - -describe('MultiInputRows', () => { - const props = { - id: 'test', - }; - const values = { - values: ['a', 'b', 'c'], - addedNewRow: false, - hasEmptyValues: false, - hasOnlyOneValue: false, - }; - const actions = { - addValue: jest.fn(), - editValue: jest.fn(), - deleteValue: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('initializes MultiInputRowsLogic with a keyed ID and initialValues', () => { - shallow(); - expect(MultiInputRowsLogic).toHaveBeenCalledWith({ id: 'lorem', values: ['ipsum'] }); - }); - - it('renders a InputRow row for each value', () => { - const wrapper = shallow(); - - expect(wrapper.find(InputRow)).toHaveLength(3); - expect(wrapper.find(InputRow).at(0).prop('value')).toEqual('a'); - expect(wrapper.find(InputRow).at(1).prop('value')).toEqual('b'); - expect(wrapper.find(InputRow).at(2).prop('value')).toEqual('c'); - }); - - it('focuses the first input row on load, but focuses new input rows on add', () => { - setMockValues({ ...values, addedNewRow: false }); - const wrapper = shallow(); - - expect(wrapper.find(InputRow).first().prop('autoFocus')).toEqual(true); - expect(wrapper.find(InputRow).last().prop('autoFocus')).toEqual(false); - - setMockValues({ ...values, addedNewRow: true }); - rerender(wrapper); - - expect(wrapper.find(InputRow).first().prop('autoFocus')).toEqual(false); - expect(wrapper.find(InputRow).last().prop('autoFocus')).toEqual(true); - }); - - it('calls editValue when the InputRow value changes', () => { - const wrapper = shallow(); - wrapper.find(InputRow).at(0).simulate('change', 'new value'); - - expect(actions.editValue).toHaveBeenCalledWith(0, 'new value'); - }); - - it('calls deleteValue when the InputRow calls onDelete', () => { - const wrapper = shallow(); - wrapper.find(InputRow).at(2).simulate('delete'); - - expect(actions.deleteValue).toHaveBeenCalledWith(2); - }); - - it('calls addValue when the Add Value button is clicked', () => { - const wrapper = shallow(); - wrapper.find('[data-test-subj="addInputRowButton"]').simulate('click'); - - expect(actions.addValue).toHaveBeenCalled(); - }); - - it('disables the add button if any value fields are empty', () => { - setMockValues({ - ...values, - values: ['a', '', 'c'], - hasEmptyValues: true, - }); - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="addInputRowButton"]'); - - expect(button.prop('isDisabled')).toEqual(true); - }); - - describe('onSubmit', () => { - const onSubmit = jest.fn(); - const preventDefault = jest.fn(); - - it('renders a form component', () => { - const wrapper = shallow(); - - expect(wrapper.prop('component')).toEqual('form'); - }); - - it('calls the passed onSubmit callback when the form is submitted', () => { - setMockValues({ ...values, values: ['some value'] }); - const wrapper = shallow(); - - wrapper.simulate('submit', { preventDefault }); - - expect(preventDefault).toHaveBeenCalled(); - expect(onSubmit).toHaveBeenCalledWith(['some value']); - }); - - it('does not render a form component or onSubmit event if onSubmit is not passed', () => { - const wrapper = shallow(); - - expect(wrapper.prop('component')).toEqual('div'); - expect(wrapper.prop('onSubmit')).toBeUndefined(); - }); - - describe('submit button', () => { - it('does not render the submit button if onSubmit is not passed', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="submitInputValuesButton"]').exists()).toBe(false); - }); - - it('does not render the submit button if showSubmitButton is false', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('[data-test-subj="submitInputValuesButton"]').exists()).toBe(false); - }); - - it('disables the submit button if no value fields have been filled', () => { - setMockValues({ - ...values, - values: [''], - hasOnlyOneValue: true, - hasEmptyValues: true, - }); - const wrapper = shallow(); - const button = wrapper.find('[data-test-subj="submitInputValuesButton"]'); - - expect(button.prop('isDisabled')).toEqual(true); - }); - }); - }); - - describe('onChange', () => { - let wrapper: ShallowWrapper; - const onChange = jest.fn(); - - beforeEach(() => { - wrapper = shallow(); - }); - - it('does not call on change on mount', () => { - expect(onChange).not.toHaveBeenCalled(); - }); - - it('returns the current values dynamically on change', () => { - setMockValues({ ...values, values: ['updated'] }); - rerender(wrapper); - - expect(onChange).toHaveBeenCalledWith(['updated']); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx deleted file mode 100644 index f80b9cc07a218..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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, { useRef } from 'react'; - -import { useValues, useActions } from 'kea'; -import useUpdateEffect from 'react-use/lib/useUpdateEffect'; - -import { EuiForm, EuiButton, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; - -import { CONTINUE_BUTTON_LABEL } from '../../../shared/constants'; - -import { - ADD_VALUE_BUTTON_LABEL, - DELETE_ROW_VALUE_BUTTON_LABEL, - INPUT_ROW_PLACEHOLDER, - INPUT_ROW_CONTAINER_ARIA_LABEL, -} from './constants'; -import { InputRow } from './input_row'; -import { MultiInputRowsLogic } from './multi_input_rows_logic'; -import { filterEmptyValues } from './utils'; - -interface Props { - id: string; - initialValues?: string[]; - onChange?(values: string[]): void; - onSubmit?(values: string[]): void; - showSubmitButton?: boolean; - submitButtonText?: string; - addRowText?: string; - deleteRowLabel?: string; - inputPlaceholder?: string; -} - -export const MultiInputRows: React.FC = ({ - id, - initialValues = [''], - onChange, - onSubmit, - showSubmitButton = true, - submitButtonText = CONTINUE_BUTTON_LABEL, - addRowText = ADD_VALUE_BUTTON_LABEL, - deleteRowLabel, - inputPlaceholder = INPUT_ROW_PLACEHOLDER, -}) => { - const logic = MultiInputRowsLogic({ id, values: initialValues }); - const { values, addedNewRow, hasEmptyValues, hasOnlyOneValue } = useValues(logic); - const { addValue, editValue, deleteValue } = useActions(logic); - const valuesContainerRef = useRef(null); - - useUpdateEffect(() => { - if (onChange) { - onChange(filterEmptyValues(values)); - } - }, [values]); - - return ( - { - e.preventDefault(); - onSubmit(filterEmptyValues(values)); - } - : undefined - } - > -
    - {values.map((value: string, index: number) => { - const firstRow = index === 0; - const lastRow = index === values.length - 1; - return ( - editValue(index, newValue)} - onDelete={() => { - deleteValue(index); - valuesContainerRef.current?.focus(); - }} - disableDelete={hasOnlyOneValue} - deleteLabel={deleteRowLabel || DELETE_ROW_VALUE_BUTTON_LABEL(index + 1)} - /> - ); - })} -
    - - {addRowText} - - {showSubmitButton && onSubmit && ( - <> - - - {submitButtonText} - - - )} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts deleted file mode 100644 index de439349bd15f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 { Logic } from 'kea'; - -import { MultiInputRowsLogic } from './multi_input_rows_logic'; - -describe('MultiInputRowsLogic', () => { - const { mount } = new LogicMounter(MultiInputRowsLogic); - - const MOCK_VALUES = ['a', 'b', 'c']; - - const DEFAULT_PROPS = { - id: 'test', - values: MOCK_VALUES, - }; - const DEFAULT_VALUES = { - values: MOCK_VALUES, - addedNewRow: false, - hasEmptyValues: false, - hasOnlyOneValue: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values passed from props', () => { - const logic = mount({}, DEFAULT_PROPS); - expect(logic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - let logic: Logic; - - beforeEach(() => { - logic = mount({}, DEFAULT_PROPS); - }); - - afterEach(() => { - // Should not mutate the original array - expect(logic.values.values).not.toBe(MOCK_VALUES); // Would fail if we did not clone a new array - }); - - describe('addValue', () => { - it('appends an empty string to the values array & sets addedNewRow to true', () => { - logic.actions.addValue(); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - addedNewRow: true, - hasEmptyValues: true, - values: ['a', 'b', 'c', ''], - }); - }); - }); - - describe('deleteValue', () => { - it('deletes the value at the specified array index', () => { - logic.actions.deleteValue(1); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - values: ['a', 'c'], - }); - }); - }); - - describe('editValue', () => { - it('edits the value at the specified array index', () => { - logic.actions.editValue(2, 'z'); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - values: ['a', 'b', 'z'], - }); - }); - }); - }); - - describe('selectors', () => { - describe('hasEmptyValues', () => { - it('returns true if values has any empty strings', () => { - const logic = mount({}, { ...DEFAULT_PROPS, values: ['', '', ''] }); - - expect(logic.values.hasEmptyValues).toEqual(true); - }); - }); - - describe('hasOnlyOneValue', () => { - it('returns true if values only has one item', () => { - const logic = mount({}, { ...DEFAULT_PROPS, values: ['test'] }); - - expect(logic.values.hasOnlyOneValue).toEqual(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts deleted file mode 100644 index 068e74be1eea8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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'; - -interface MultiInputRowsValues { - values: string[]; - addedNewRow: boolean; - hasEmptyValues: boolean; - hasOnlyOneValue: boolean; -} - -interface MultiInputRowsActions { - addValue(): void; - deleteValue(indexToDelete: number): { indexToDelete: number }; - editValue(index: number, newValueValue: string): { index: number; newValueValue: string }; -} - -interface MultiInputRowsProps { - values: string[]; - id: string; -} - -export const MultiInputRowsLogic = kea< - MakeLogicType ->({ - path: (key: string) => ['enterprise_search', 'app_search', 'multi_input_rows_logic', key], - key: (props) => props.id, - actions: () => ({ - addValue: true, - deleteValue: (indexToDelete) => ({ indexToDelete }), - editValue: (index, newValueValue) => ({ index, newValueValue }), - }), - reducers: ({ props }) => ({ - values: [ - props.values, - { - // @ts-expect-error upgrade typescript v5.1.6 - addValue: (state) => [...state, ''], - // @ts-expect-error upgrade typescript v5.1.6 - deleteValue: (state, { indexToDelete }) => { - const newState = [...state]; - newState.splice(indexToDelete, 1); - return newState; - }, - // @ts-expect-error upgrade typescript v5.1.6 - editValue: (state, { index, newValueValue }) => { - const newState = [...state]; - newState[index] = newValueValue; - return newState; - }, - }, - ], - addedNewRow: [ - false, - { - addValue: () => true, - }, - ], - }), - selectors: { - hasEmptyValues: [(selectors) => [selectors.values], (values) => values.indexOf('') >= 0], - hasOnlyOneValue: [(selectors) => [selectors.values], (values) => values.length <= 1], - }, -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/utils.test.ts deleted file mode 100644 index 0946890c40dfa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/utils.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 { filterEmptyValues } from './utils'; - -describe('filterEmptyValues', () => { - it('filters out all empty strings from the array', () => { - const values = ['', 'a', '', 'b', '', 'c', '']; - expect(filterEmptyValues(values)).toEqual(['a', 'b', 'c']); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/utils.ts deleted file mode 100644 index 5ecefb240e17d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 const filterEmptyValues = (values: string[]) => { - return values.filter((value) => value.length); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts deleted file mode 100644 index 482c1a58faa9c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx deleted file mode 100644 index add77d2d1e6c3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { NotFoundPrompt } from '../../../shared/not_found'; -import { SendAppSearchTelemetry } from '../../../shared/telemetry'; -import { AppSearchPageTemplate } from '../layout'; - -import { NotFound } from '.'; - -describe('NotFound', () => { - const wrapper = shallow(); - - it('renders the shared not found prompt', () => { - expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); - }); - - it('renders a telemetry error event', () => { - expect(wrapper.find(SendAppSearchTelemetry).prop('action')).toEqual('error'); - }); - - it('passes optional preceding page chrome', () => { - wrapper.setProps({ pageChrome: ['Engines', 'some-engine'] }); - - expect(wrapper.find(AppSearchPageTemplate).prop('pageChrome')).toEqual([ - 'Engines', - 'some-engine', - '404', - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx deleted file mode 100644 index 3426a5cd3405c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { PageTemplateProps } from '../../../shared/layout'; -import { NotFoundPrompt } from '../../../shared/not_found'; -import { SendAppSearchTelemetry } from '../../../shared/telemetry'; -import { AppSearchPageTemplate } from '../layout'; - -export const NotFound: React.FC = ({ pageChrome = [] }) => { - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/i18n.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/i18n.ts deleted file mode 100644 index a1b1f6769beaf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/i18n.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const QUERY_TESTER_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.queryTesterTitle', - { - defaultMessage: 'Query tester', - } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/index.ts deleted file mode 100644 index b2b8ad0dd1255..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { QueryTesterFlyout } from './query_tester_flyout'; -export { QueryTesterButton } from './query_tester_button'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester.test.tsx deleted file mode 100644 index a9e2b7129c781..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt, EuiFieldSearch } from '@elastic/eui'; - -import { SchemaType } from '../../../shared/schema/types'; -import { Result } from '../result'; - -import { QueryTester } from './query_tester'; - -describe('QueryTester', () => { - const values = { - searchQuery: 'foo', - searchResults: [{ id: { raw: '1' } }, { id: { raw: '2' } }, { id: { raw: '3' } }], - searchDataLoading: false, - engine: { - schema: { - foo: SchemaType.Text, - }, - }, - }; - - const actions = { - search: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders with a search box and results', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFieldSearch).prop('value')).toBe('foo'); - expect(wrapper.find(EuiFieldSearch).prop('isLoading')).toBe(false); - expect(wrapper.find(Result)).toHaveLength(3); - }); - - it('will update the search term in state when the user updates the search box', () => { - const wrapper = shallow(); - wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'bar' } }); - expect(actions.search).toHaveBeenCalledWith('bar'); - }); - - it('will render an empty prompt when there are no results', () => { - setMockValues({ - ...values, - searchResults: [], - }); - const wrapper = shallow(); - wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'bar' } }); - expect(wrapper.find(Result)).toHaveLength(0); - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester.tsx deleted file mode 100644 index 374b6bd1a77b3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiFieldSearch, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EngineLogic } from '../engine'; -import { Result } from '../result'; -import { SearchLogic } from '../search'; - -export const QueryTester: React.FC = () => { - const logic = SearchLogic({ id: 'query-tester' }); - const { searchQuery, searchResults, searchDataLoading } = useValues(logic); - const { search } = useActions(logic); - const { engine } = useValues(EngineLogic); - - return ( - <> - search(e.target.value)} - isLoading={searchDataLoading} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.queryTester.searchPlaceholder', - { defaultMessage: 'Search engine documents' } - )} - fullWidth - autoFocus - /> - - {searchResults.length > 0 ? ( - searchResults.map((result) => { - const id = result.id.raw; - - return ( - - - - - ); - }) - ) : ( - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_button.test.tsx deleted file mode 100644 index 4d2c3286ff516..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_button.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { EuiButtonEmpty } from '@elastic/eui'; - -import { QueryTesterFlyout, QueryTesterButton } from '.'; - -describe('QueryTesterButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiButtonEmpty).exists()).toBe(true); - expect(wrapper.find(QueryTesterFlyout).exists()).toBe(false); - }); - - it('will render a QueryTesterFlyout when pressed and close on QueryTesterFlyout close', () => { - const wrapper = shallow(); - wrapper.find(EuiButtonEmpty).simulate('click'); - expect(wrapper.find(QueryTesterFlyout).exists()).toBe(true); - - wrapper.find(QueryTesterFlyout).simulate('close'); - expect(wrapper.find(QueryTesterFlyout).exists()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_button.tsx deleted file mode 100644 index 89381914b6db6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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, { useState } from 'react'; - -import { EuiButtonEmpty } from '@elastic/eui'; - -import { QUERY_TESTER_TITLE } from './i18n'; - -import { QueryTesterFlyout } from '.'; - -export const QueryTesterButton: React.FC = () => { - const [isQueryTesterOpen, setIsQueryTesterOpen] = useState(false); - return ( - <> - setIsQueryTesterOpen(!isQueryTesterOpen)} - > - {QUERY_TESTER_TITLE} - - {isQueryTesterOpen && setIsQueryTesterOpen(false)} />} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_flyout.test.tsx deleted file mode 100644 index 8c25589f04639..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_flyout.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { EuiFlyout } from '@elastic/eui'; - -import { QueryTester } from './query_tester'; -import { QueryTesterFlyout } from './query_tester_flyout'; - -describe('QueryTesterFlyout', () => { - const onClose = jest.fn(); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(QueryTester).exists()).toBe(true); - expect(wrapper.find(EuiFlyout).prop('onClose')).toEqual(onClose); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_flyout.tsx deleted file mode 100644 index d419bef472de3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/query_tester/query_tester_flyout.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; - -import { QUERY_TESTER_TITLE } from './i18n'; -import { QueryTester } from './query_tester'; - -interface Props { - onClose: () => void; -} - -export const QueryTesterFlyout: React.FC = ({ onClose }) => { - return ( - - - -

    {QUERY_TESTER_TITLE}

    -
    -
    - - - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx deleted file mode 100644 index 0af20982bb970..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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, ShallowWrapper } from 'enzyme'; - -import { EuiAccordion } from '@elastic/eui'; - -import { BoostIcon, ValueBadge } from '../components'; -import { BoostType } from '../types'; - -import { BoostItem } from './boost_item'; -import { BoostItemContent } from './boost_item_content'; - -describe('BoostItem', () => { - const boost = { - factor: 2, - type: BoostType.Value, - newBoost: true, - value: [''], - }; - - let wrapper: ShallowWrapper; - let accordian: ShallowWrapper; - - beforeAll(() => { - wrapper = shallow(); - accordian = wrapper.find(EuiAccordion) as ShallowWrapper; - }); - - it('renders an accordion as open if it is a newly created boost', () => { - expect(accordian.prop('initialIsOpen')).toEqual(boost.newBoost); - }); - - it('renders an accordion button which shows a summary of the boost', () => { - const buttonContent = shallow( - accordian.prop('buttonContent') as React.ReactElement - ) as ShallowWrapper; - - expect(buttonContent.find(BoostIcon).prop('type')).toEqual('value'); - expect(buttonContent.find(ValueBadge).children().text()).toEqual('2'); - }); - - it('renders boost content inside of the accordion', () => { - const content = wrapper.find(BoostItemContent); - expect(content.props()).toEqual({ - boost, - index: 1, - name: 'foo', - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx deleted file mode 100644 index 2954ffc5e8c2f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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, { useMemo } from 'react'; - -import { EuiFlexItem, EuiAccordion, EuiFlexGroup, EuiHideFor } from '@elastic/eui'; - -import { BoostIcon, ValueBadge } from '../components'; -import { BOOST_TYPE_TO_DISPLAY_MAP } from '../constants'; -import { Boost } from '../types'; - -import { BoostItemContent } from './boost_item_content'; -import { getBoostSummary } from './get_boost_summary'; - -interface Props { - boost: Boost; - id: string; - index: number; - name: string; -} - -export const BoostItem: React.FC = ({ id, boost, index, name }) => { - const summary = useMemo(() => getBoostSummary(boost), [boost]); - - return ( - - - - - {BOOST_TYPE_TO_DISPLAY_MAP[boost.type]} - - {summary} - - - {boost.factor} - - - } - paddingSize="s" - > - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx deleted file mode 100644 index 794c644f3d40b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton, EuiRange } from '@elastic/eui'; - -import { BoostType } from '../../types'; - -import { BoostItemContent } from './boost_item_content'; -import { FunctionalBoostForm } from './functional_boost_form'; -import { ProximityBoostForm } from './proximity_boost_form'; -import { ValueBoostForm } from './value_boost_form'; - -describe('BoostItemContent', () => { - const actions = { - updateBoostFactor: jest.fn(), - deleteBoost: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders a value boost form if the provided boost is "value" boost', () => { - const boost = { - factor: 2, - type: 'value' as BoostType, - value: [''], - }; - - const wrapper = shallow(); - - expect(wrapper.find(ValueBoostForm).exists()).toBe(true); - expect(wrapper.find(FunctionalBoostForm).exists()).toBe(false); - expect(wrapper.find(ProximityBoostForm).exists()).toBe(false); - }); - - it('renders a functional boost form if the provided boost is "functional" boost', () => { - const boost = { - factor: 10, - type: 'functional' as BoostType, - }; - - const wrapper = shallow(); - - expect(wrapper.find(ValueBoostForm).exists()).toBe(false); - expect(wrapper.find(FunctionalBoostForm).exists()).toBe(true); - expect(wrapper.find(ProximityBoostForm).exists()).toBe(false); - }); - - it('renders a proximity boost form if the provided boost is "proximity" boost', () => { - const boost = { - factor: 8, - type: 'proximity' as BoostType, - }; - - const wrapper = shallow(); - - expect(wrapper.find(ValueBoostForm).exists()).toBe(false); - expect(wrapper.find(FunctionalBoostForm).exists()).toBe(false); - expect(wrapper.find(ProximityBoostForm).exists()).toBe(true); - }); - - it("renders an impact slider that can be used to update the boost's 'factor'", () => { - const boost = { - factor: 8, - type: 'proximity' as BoostType, - }; - - const wrapper = shallow(); - const impactSlider = wrapper.find(EuiRange); - expect(impactSlider.prop('value')).toBe(8); - - impactSlider.simulate('change', { target: { value: '2' } }); - - expect(actions.updateBoostFactor).toHaveBeenCalledWith('foo', 3, 2); - }); - - it("will delete the current boost if the 'Delete boost' button is clicked", () => { - const boost = { - factor: 8, - type: 'proximity' as BoostType, - }; - - const wrapper = shallow(); - wrapper.find(EuiButton).simulate('click'); - - expect(actions.deleteBoost).toHaveBeenCalledWith('foo', 3); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx deleted file mode 100644 index 44c5f276cd20c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiButton, EuiFormRow, EuiPanel, EuiRange, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { RelevanceTuningLogic } from '../..'; -import { Boost, BoostType, FunctionalBoost, ProximityBoost, ValueBoost } from '../../types'; - -import { FunctionalBoostForm } from './functional_boost_form'; -import { ProximityBoostForm } from './proximity_boost_form'; -import { ValueBoostForm } from './value_boost_form'; - -interface Props { - boost: Boost; - index: number; - name: string; -} - -export const BoostItemContent: React.FC = ({ boost, index, name }) => { - const { deleteBoost, updateBoostFactor } = useActions(RelevanceTuningLogic); - const { type } = boost; - - const getBoostForm = () => { - switch (type) { - case BoostType.Value: - return ; - case BoostType.Functional: - return ; - case BoostType.Proximity: - return ; - } - }; - - return ( - - - {getBoostForm()} - - - - updateBoostFactor( - name, - index, - parseFloat((e as React.ChangeEvent).target.value) - ) - } - showInput - compressed - fullWidth - /> - - deleteBoost(name, index)}> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.deleteBoostButtonLabel', - { - defaultMessage: 'Delete boost', - } - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx deleted file mode 100644 index 4a540e09287ed..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiSelect } from '@elastic/eui'; - -import { FunctionalBoost, BoostOperation, BoostType, FunctionalBoostFunction } from '../../types'; - -import { FunctionalBoostForm } from './functional_boost_form'; - -describe('FunctionalBoostForm', () => { - const boost: FunctionalBoost = { - value: undefined, - factor: 2, - type: 'functional' as BoostType, - function: 'logarithmic' as FunctionalBoostFunction, - operation: 'multiply' as BoostOperation, - }; - - const actions = { - updateBoostSelectOption: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - const functionSelect = (wrapper: ShallowWrapper) => wrapper.find(EuiSelect).at(0); - const operationSelect = (wrapper: ShallowWrapper) => wrapper.find(EuiSelect).at(1); - - it('renders select boxes with values from the provided boost selected', () => { - const wrapper = shallow(); - expect(functionSelect(wrapper).prop('value')).toEqual('logarithmic'); - expect(operationSelect(wrapper).prop('value')).toEqual('multiply'); - }); - - it('will update state when a user makes a selection', () => { - const wrapper = shallow(); - - functionSelect(wrapper).simulate('change', { - target: { - value: 'exponential', - }, - }); - expect(actions.updateBoostSelectOption).toHaveBeenCalledWith( - 'foo', - 3, - 'function', - 'exponential' - ); - - operationSelect(wrapper).simulate('change', { - target: { - value: 'add', - }, - }); - expect(actions.updateBoostSelectOption).toHaveBeenCalledWith('foo', 3, 'operation', 'add'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx deleted file mode 100644 index ebd826dcd27ef..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiFormRow, EuiSelect } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { RelevanceTuningLogic } from '../..'; -import { - BOOST_OPERATION_DISPLAY_MAP, - FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP, -} from '../../constants'; -import { - FunctionalBoost, - BoostFunction, - BoostOperation, - BoostType, - FunctionalBoostFunction, -} from '../../types'; - -interface Props { - boost: FunctionalBoost; - index: number; - name: string; -} - -const functionOptions = Object.values(FunctionalBoostFunction).map((boostFunction) => ({ - value: boostFunction, - text: FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP[boostFunction as FunctionalBoostFunction], -})); - -const operationOptions = Object.values(BoostOperation).map((boostOperation) => ({ - value: boostOperation, - text: BOOST_OPERATION_DISPLAY_MAP[boostOperation], -})); - -export const FunctionalBoostForm: React.FC = ({ boost, index, name }) => { - const { updateBoostSelectOption } = useActions(RelevanceTuningLogic); - return ( - <> - - - updateBoostSelectOption(name, index, 'function', e.target.value as BoostFunction) - } - fullWidth - /> - - - - updateBoostSelectOption(name, index, 'operation', e.target.value as BoostOperation) - } - fullWidth - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/index.ts deleted file mode 100644 index 1a13c486ca523..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { BoostItemContent } from './boost_item_content'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx deleted file mode 100644 index 776a21c5acb1b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiFieldText, EuiSelect } from '@elastic/eui'; - -import { ProximityBoost, BoostType, ProximityBoostFunction } from '../../types'; - -import { ProximityBoostForm } from './proximity_boost_form'; - -describe('ProximityBoostForm', () => { - const boost: ProximityBoost = { - value: undefined, - operation: undefined, - factor: 2, - type: 'proximity' as BoostType, - function: 'linear' as ProximityBoostFunction, - center: '2', - }; - - const actions = { - updateBoostSelectOption: jest.fn(), - updateBoostCenter: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - const functionSelect = (wrapper: ShallowWrapper) => wrapper.find(EuiSelect); - const centerInput = (wrapper: ShallowWrapper) => wrapper.find(EuiFieldText); - - it('renders input with values from the provided boost', () => { - const wrapper = shallow(); - expect(functionSelect(wrapper).prop('value')).toEqual('linear'); - expect(centerInput(wrapper).prop('defaultValue')).toEqual('2'); - }); - - describe('various boost values', () => { - const renderWithBoostValues = (boostValues: { - center?: ProximityBoost['center']; - function?: ProximityBoost['function']; - }) => { - return shallow( - - ); - }; - - it('will set the center value as a string if the value is a number', () => { - const wrapper = renderWithBoostValues({ center: 0 }); - expect(centerInput(wrapper).prop('defaultValue')).toEqual('0'); - }); - - it('will set the center value as an empty string if the value is undefined', () => { - const wrapper = renderWithBoostValues({ center: undefined }); - expect(centerInput(wrapper).prop('defaultValue')).toEqual(''); - }); - - it('will set the function to Guaussian if it is not already set', () => { - const wrapper = renderWithBoostValues({ function: undefined }); - expect(functionSelect(wrapper).prop('value')).toEqual('gaussian'); - }); - }); - - it('will update state when a user enters input', () => { - const wrapper = shallow(); - - functionSelect(wrapper).simulate('change', { - target: { - value: 'exponential', - }, - }); - expect(actions.updateBoostSelectOption).toHaveBeenCalledWith( - 'foo', - 3, - 'function', - 'exponential' - ); - - centerInput(wrapper).simulate('change', { - target: { - value: '5', - }, - }); - expect(actions.updateBoostCenter).toHaveBeenCalledWith('foo', 3, '5'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.tsx deleted file mode 100644 index b236666ac9393..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { RelevanceTuningLogic } from '../..'; -import { PROXIMITY_BOOST_FUNCTION_DISPLAY_MAP } from '../../constants'; -import { BoostType, ProximityBoost, ProximityBoostFunction } from '../../types'; - -interface Props { - boost: ProximityBoost; - index: number; - name: string; -} - -export const ProximityBoostForm: React.FC = ({ boost, index, name }) => { - const { updateBoostSelectOption, updateBoostCenter } = useActions(RelevanceTuningLogic); - - const currentBoostCenter = boost.center !== undefined ? boost.center.toString() : ''; - const currentBoostFunction = boost.function || ProximityBoostFunction.Gaussian; - - const functionOptions = Object.values(ProximityBoostFunction).map((boostFunction) => ({ - value: boostFunction, - text: PROXIMITY_BOOST_FUNCTION_DISPLAY_MAP[boostFunction as ProximityBoostFunction], - })); - - return ( - <> - - - updateBoostSelectOption( - name, - index, - 'function', - e.target.value as ProximityBoostFunction - ) - } - fullWidth - /> - - - updateBoostCenter(name, index, e.target.value)} - fullWidth - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx deleted file mode 100644 index 292be35055e37..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { MultiInputRows } from '../../../multi_input_rows'; - -import { ValueBoost, BoostType } from '../../types'; - -import { ValueBoostForm } from './value_boost_form'; - -describe('ValueBoostForm', () => { - const boost: ValueBoost = { - operation: undefined, - function: undefined, - factor: 2, - type: 'value' as BoostType, - value: [], - }; - - const actions = { - updateBoostValue: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(MultiInputRows).exists()).toBe(true); - }); - - it('updates the boost value whenever the MultiInputRows form component updates', () => { - const wrapper = shallow(); - wrapper.find(MultiInputRows).simulate('change', ['bar', 'baz']); - - expect(actions.updateBoostValue).toHaveBeenCalledWith('foo', 3, ['bar', 'baz']); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx deleted file mode 100644 index 10da642cf7248..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 } from 'kea'; - -import { RelevanceTuningLogic } from '../..'; -import { MultiInputRows } from '../../../multi_input_rows'; - -import { ValueBoost } from '../../types'; - -interface Props { - boost: ValueBoost; - index: number; - name: string; -} - -export const ValueBoostForm: React.FC = ({ boost, index, name }) => { - const { updateBoostValue } = useActions(RelevanceTuningLogic); - const values = boost.value; - - return ( - updateBoostValue(name, index, updatedValues)} - id={`${name}BoostValue-${index}`} - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.scss deleted file mode 100644 index 0e9b2b1035b36..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.scss +++ /dev/null @@ -1,28 +0,0 @@ -.boosts { - &__select { - min-width: $euiSizeXXL * 4; - } - - &__itemButton { - width: 100%; - } - - &__item { - margin-top: $euiSize; - - & + & { - margin-top: $euiSizeS; - } - } -} - -.boostSelectOption { - .euiContextMenuItem__text { - display: flex; - align-items: center; - - .euiToken { - margin-right: $euiSizeS; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx deleted file mode 100644 index a7c7002fbf273..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiSuperSelect } from '@elastic/eui'; - -import { SchemaType } from '../../../../shared/schema/types'; - -import { BoostType } from '../types'; - -import { BoostItem } from './boost_item'; -import { Boosts } from './boosts'; - -describe('Boosts', () => { - const actions = { - addBoost: jest.fn(), - }; - - beforeAll(() => { - setMockActions(actions); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const props = { - name: 'foo', - type: SchemaType.Number, - }; - - it('renders a select box that allows users to create boosts of various types', () => { - const wrapper = shallow(); - - const select = wrapper.find(EuiSuperSelect); - expect(select.prop('options').map((o: any) => o.value)).toEqual([ - 'add-boost', - 'functional', - 'proximity', - 'value', - ]); - }); - - it('will not render functional or proximity options if "type" prop is "text"', () => { - const wrapper = shallow( - - ); - - const select = wrapper.find(EuiSuperSelect); - expect(select.prop('options').map((o: any) => o.value)).toEqual(['add-boost', 'value']); - }); - - it('will not render functional or value options if "type" prop is "geolocation"', () => { - const wrapper = shallow( - - ); - - const select = wrapper.find(EuiSuperSelect); - expect(select.prop('options').map((o: any) => o.value)).toEqual(['add-boost', 'proximity']); - }); - - it('will not render functional option if "type" prop is "date"', () => { - const wrapper = shallow( - - ); - - const select = wrapper.find(EuiSuperSelect); - expect(select.prop('options').map((o: any) => o.value)).toEqual([ - 'add-boost', - 'proximity', - 'value', - ]); - }); - - it('will add a boost of the selected type when a selection is made', () => { - const wrapper = shallow(); - - wrapper.find(EuiSuperSelect).simulate('change', 'functional'); - - expect(actions.addBoost).toHaveBeenCalledWith('foo', 'functional'); - }); - - it('will render a list of boosts', () => { - const boost1 = { - factor: 2, - type: 'value' as BoostType, - value: [''], - }; - const boost2 = { - factor: 10, - type: 'functional' as BoostType, - }; - const boost3 = { - factor: 8, - type: 'proximity' as BoostType, - }; - - const wrapper = shallow( - - ); - - const boostItems = wrapper.find(BoostItem); - expect(boostItems.at(0).prop('boost')).toEqual(boost1); - expect(boostItems.at(1).prop('boost')).toEqual(boost2); - expect(boostItems.at(2).prop('boost')).toEqual(boost3); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx deleted file mode 100644 index 16249f8a9b370..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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, { useMemo } from 'react'; - -import { useActions } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle, EuiSuperSelect } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { SchemaType } from '../../../../shared/schema/types'; - -import { BoostIcon } from '../components'; -import { FUNCTIONAL_DISPLAY, PROXIMITY_DISPLAY, VALUE_DISPLAY } from '../constants'; -import { RelevanceTuningLogic } from '../relevance_tuning_logic'; -import { Boost, BoostType } from '../types'; - -import { BoostItem } from './boost_item'; - -import './boosts.scss'; - -const BASE_OPTIONS = [ - { - value: 'add-boost', - inputDisplay: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addBoostDropDownOptionLabel', - { - defaultMessage: 'Add boost', - } - ), - disabled: true, - }, - { - value: BoostType.Functional, - inputDisplay: ( - <> - - {FUNCTIONAL_DISPLAY} - - ), - }, - { - value: BoostType.Proximity, - inputDisplay: ( - <> - - {PROXIMITY_DISPLAY} - - ), - }, - { - value: BoostType.Value, - inputDisplay: ( - <> - - {VALUE_DISPLAY} - - ), - }, -]; - -const filterInvalidOptions = (value: BoostType, type: SchemaType) => { - // Proximity and Functional boost types are not valid for text fields - if (type === SchemaType.Text && [BoostType.Proximity, BoostType.Functional].includes(value)) - return false; - // Value and Functional boost types are not valid for geolocation fields - if (type === SchemaType.Geolocation && [BoostType.Functional, BoostType.Value].includes(value)) - return false; - // Functional boosts are not valid for date fields - if (type === SchemaType.Date && value === BoostType.Functional) return false; - return true; -}; - -interface Props { - name: string; - type: SchemaType; - boosts?: Boost[]; -} - -export const Boosts: React.FC = ({ name, type, boosts = [] }) => { - const { addBoost } = useActions(RelevanceTuningLogic); - - const selectOptions = useMemo( - () => BASE_OPTIONS.filter((option) => filterInvalidOptions(option.value as BoostType, type)), - [type] - ); - - return ( - - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.title', - { - defaultMessage: 'Boosts', - } - )} -

    -
    -
    - - addBoost(name, value as BoostType)} - /> - -
    - {boosts.map((boost, index) => ( - - ))} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.test.ts deleted file mode 100644 index 4d78fe8f06739..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 { Boost, BoostFunction, BoostType, BoostOperation, FunctionalBoostFunction } from '../types'; - -import { getBoostSummary } from './get_boost_summary'; - -describe('getBoostSummary', () => { - describe('when the boost type is "value"', () => { - const boost: Boost = { - type: BoostType.Value, - value: ['1', '2'], - factor: 5, - }; - - it('creates a summary that is the joined values', () => { - expect(getBoostSummary(boost)).toEqual('1,2'); - }); - - it('creates an empty summary if there is no value', () => { - expect( - getBoostSummary({ - ...boost, - value: undefined, - }) - ).toEqual(''); - }); - - it('filters out empty values', () => { - expect( - getBoostSummary({ - ...boost, - value: [' ', '', 'foo', '', 'bar'], - }) - ).toEqual('foo,bar'); - }); - }); - - describe('when the boost type is "proximity"', () => { - const boost: Boost = { - type: BoostType.Proximity, - function: 'gaussian' as BoostFunction, - factor: 5, - }; - - it('creates a summary that is just the name of the function', () => { - expect(getBoostSummary(boost)).toEqual('gaussian'); - }); - - it('creates an empty summary if there is no function', () => { - expect( - getBoostSummary({ - ...boost, - function: undefined, - }) - ).toEqual(''); - }); - }); - - describe('when the boost type is "functional"', () => { - const boost: Boost = { - type: BoostType.Functional, - function: FunctionalBoostFunction.Logarithmic, - operation: BoostOperation.Add, - factor: 5, - }; - - it('creates a summary that is name of the function and operation', () => { - expect(getBoostSummary(boost)).toEqual('logarithmic add'); - }); - - it('prints empty if function or operation is missing', () => { - expect(getBoostSummary({ ...boost, function: undefined })).toEqual(BoostOperation.Add); - expect(getBoostSummary({ ...boost, operation: undefined })).toEqual( - FunctionalBoostFunction.Logarithmic - ); - expect(getBoostSummary({ ...boost, function: undefined, operation: undefined })).toEqual(''); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.ts deleted file mode 100644 index 71b1a6136cf65..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 { Boost, BoostType } from '../types'; - -export const getBoostSummary = (boost: Boost): string => { - if (boost.type === BoostType.Value) { - return !boost.value ? '' : boost.value.filter((v) => v.trim() !== '').join(','); - } else if (boost.type === BoostType.Proximity) { - return boost.function || ''; - } else { - return [boost.function || '', boost.operation || ''].join(' ').trim(); - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/index.ts deleted file mode 100644 index dc269132769b8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { Boosts } from './boosts'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/boost_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/boost_icon.test.tsx deleted file mode 100644 index 15281be9ddd6c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/boost_icon.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { EuiToken } from '@elastic/eui'; - -import { BoostType } from '../types'; - -import { BoostIcon } from './boost_icon'; - -describe('BoostIcon', () => { - it('renders a token according to the provided type', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiToken).prop('iconType')).toBe('tokenNumber'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/boost_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/boost_icon.tsx deleted file mode 100644 index 8af03fd63b438..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/boost_icon.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { EuiToken } from '@elastic/eui'; - -import { BOOST_TYPE_TO_ICON_MAP } from '../constants'; -import { BoostType } from '../types'; - -interface Props { - type: BoostType; -} - -export const BoostIcon: React.FC = ({ type }) => { - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx deleted file mode 100644 index 371aa1c61095e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Add documents to tune relevance'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchRelevance) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx deleted file mode 100644 index b792dec2cba0e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyState: React.FC = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.title', { - defaultMessage: 'Add documents to tune relevance', - })} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.description', - { - defaultMessage: - 'A schema will be automatically created for you after you index some documents.', - } - )} - actions={ - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel', - { defaultMessage: 'Read the relevance tuning guide' } - )} - - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/index.ts deleted file mode 100644 index f456e11b160f6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { BoostIcon } from './boost_icon'; -export { ValueBadge } from './value_badge'; -export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts deleted file mode 100644 index 1d6749193e29a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -const STEP_01_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step01.description', - { - defaultMessage: 'Highest recall, lowest precision setting.', - } -); - -const STEP_02_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step02.description', - { - defaultMessage: - 'Default: Less than half of the terms have to match. Full typo tolerance is applied.', - } -); - -const STEP_03_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step03.description', - { - defaultMessage: - 'Increased term requirements: To match, documents must contain all terms for queries with up to 2 terms, then half if there are more. Full typo tolerance is applied.', - } -); - -const STEP_04_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step04.description', - { - defaultMessage: - 'Increased term requirements: To match, documents must contain all terms for queries with up to 3 terms, then three-quarters if there are more. Full typo tolerance is applied.', - } -); - -const STEP_05_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step05.description', - { - defaultMessage: - ' Increased term requirements: To match, documents must contain all terms for queries with up to 4 terms, then all but one if there are more. Full typo tolerance is applied.', - } -); - -const STEP_06_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step06.description', - { - defaultMessage: - 'Increased term requirements: To match, documents must contain all terms for any query. Full typo tolerance is applied.', - } -); - -const STEP_07_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step07.description', - { - defaultMessage: - 'Strictest term requirements: To match, documents must contain all terms in the same field. Full typo tolerance is applied.', - } -); - -const STEP_08_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step08.description', - { - defaultMessage: - 'Strictest term requirements: To match, documents must contain all terms in the same field. Partial typo tolerance is applied: fuzzy matching is disabled.', - } -); - -const STEP_09_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step09.description', - { - defaultMessage: - 'Strictest term requirements: To match, documents must contain all terms in the same field. Partial typo tolerance is applied: fuzzy matching and prefixing are disabled.', - } -); - -const STEP_10_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step10.description', - { - defaultMessage: - 'Strictest term requirements: To match, documents must contain all terms in the same field. Partial typo tolerance is applied: in addition to the above, contractions and hyphenations are not corrected.', - } -); - -const STEP_11_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step11.description', - { - defaultMessage: - 'Only exact matches will apply, with tolerance only for differences in capitalization.', - } -); - -export const STEP_DESCRIPTIONS = [ - undefined, // The precision number we get from the API starts with 1 instead of 0, so we leave this blank - STEP_01_DESCRIPTION, - STEP_02_DESCRIPTION, - STEP_03_DESCRIPTION, - STEP_04_DESCRIPTION, - STEP_05_DESCRIPTION, - STEP_06_DESCRIPTION, - STEP_07_DESCRIPTION, - STEP_08_DESCRIPTION, - STEP_09_DESCRIPTION, - STEP_10_DESCRIPTION, - STEP_11_DESCRIPTION, -]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts deleted file mode 100644 index 898c50e811abe..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { PrecisionSlider } from './precision_slider'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss deleted file mode 100644 index 1f6d558556444..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss +++ /dev/null @@ -1,3 +0,0 @@ -.stepDescription { - min-height: $euiSize * 4; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx deleted file mode 100644 index 0554b31c88356..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { docLinks } from '../../../../../shared/doc_links'; - -import { rerender } from '../../../../../test_helpers'; - -import { STEP_DESCRIPTIONS } from './constants'; -import { PrecisionSlider } from './precision_slider'; - -const MOCK_VALUES = { - // RelevanceTuningLogic - searchSettings: { - precision: 2, - }, -}; - -const MOCK_ACTIONS = { - // RelevanceTuningLogic - setPrecision: jest.fn(), -}; - -describe('PrecisionSlider', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - wrapper = shallow(); - }); - - describe('Range Slider', () => { - it('has the correct min and max', () => { - expect(wrapper.find('[data-test-subj="PrecisionRange"]').prop('min')).toEqual(1); - - expect(wrapper.find('[data-test-subj="PrecisionRange"]').prop('max')).toEqual(11); - }); - - it('displays the correct value', () => { - expect(wrapper.find('[data-test-subj="PrecisionRange"]').prop('value')).toEqual(2); - }); - - it('updates the precision on change', () => { - wrapper - .find('[data-test-subj="PrecisionRange"]') - .simulate('change', { target: { value: 10 } }); - - expect(MOCK_ACTIONS.setPrecision).toHaveBeenCalledWith(10); - }); - }); - - describe('Step Description', () => { - it('is visible when there is a step description', () => { - setMockValues({ - ...MOCK_VALUES, - searchSettings: { ...MOCK_VALUES.searchSettings, precision: 10 }, - }); - rerender(wrapper); - - expect(wrapper.find('[data-test-subj="StepDescription"]').render().text()).toEqual( - STEP_DESCRIPTIONS[10] - ); - }); - - it('is hidden when there is no step description', () => { - setMockValues({ ...MOCK_VALUES, precision: 14 }); - rerender(wrapper); - - expect(wrapper.contains('[data-test-subj="StepDescription"]')).toBe(false); - }); - }); - - it('contains a documentation link', () => { - const documentationLink = wrapper.find('[data-test-subj="documentationLink"]'); - - expect(documentationLink.prop('href')).toContain(docLinks.appSearchPrecision); - expect(documentationLink.prop('target')).toEqual('_blank'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx deleted file mode 100644 index d78949d0fbe74..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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 { - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiPanel, - EuiRange, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../../shared/doc_links'; -import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; - -import { STEP_DESCRIPTIONS } from './constants'; - -import './precision_slider.scss'; - -export const PrecisionSlider: React.FC = () => { - const { - searchSettings: { precision }, - } = useValues(RelevanceTuningLogic); - - const { setPrecision } = useActions(RelevanceTuningLogic); - - const stepDescription = STEP_DESCRIPTIONS[precision]; - - return ( - <> - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.title', - { - defaultMessage: 'Precision tuning', - } - )} -

    -
    - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.description', - { - defaultMessage: 'Fine tune the precision vs. recall settings on your engine.', - } - )}{' '} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link', - { - defaultMessage: 'Learn more.', - } - )} - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.recall.label', - { - defaultMessage: 'Recall', - } - )} - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.precision.label', - { - defaultMessage: 'Precision', - } - )} - - - - - { - setPrecision(parseInt((e.target as HTMLInputElement).value, 10)); - }} - min={1} - max={11} - step={1} - showRange - showTicks - fullWidth - /> - {stepDescription && ( - <> - - - {stepDescription} - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.scss deleted file mode 100644 index 853edd3fed0a3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.scss +++ /dev/null @@ -1,23 +0,0 @@ -.relevanceTuningForm { - .valueBadge { - display: inline-flex; - align-items: center; - height: $euiSizeL; - border: 1px solid $euiColorLightShade; - border-radius: $euiSizeXS; - // To match the background of EuiToken, for which there is no direct variable to - // reference - background: tintOrShade($euiColorVis1, 90%, 70%); - color: $euiColorVis1; - padding: 0 $euiSizeS; - - .euiIcon { - margin-right: $euiSizeXS; - } - } - - .valueBadge--disabled { - background: transparent; - color: $euiColorMediumShade; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.test.tsx deleted file mode 100644 index b2067e90ac94a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { ValueBadge } from '.'; - -describe('ValueBadge', () => { - it('renders', () => { - const wrapper = shallow(Hello world); - expect(wrapper.hasClass('valueBadge')).toBe(true); - expect(wrapper.text()).toEqual('Hello world'); - }); - - it('renders a disabled class', () => { - const wrapper = shallow(Test); - expect(wrapper.hasClass('valueBadge--disabled')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.tsx deleted file mode 100644 index a5e86892b4c6c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/value_badge.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 classNames from 'classnames'; - -import './value_badge.scss'; - -interface Props { - children: React.ReactNode; - disabled?: boolean; -} -export const ValueBadge: React.FC = ({ children, disabled = false }) => { - const className = classNames('valueBadge', { - 'valueBadge--disabled': disabled, - }); - return {children}; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts deleted file mode 100644 index 105ab8b9618b5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { - BoostOperation, - BoostType, - FunctionalBoost, - FunctionalBoostFunction, - ProximityBoost, - ProximityBoostFunction, - ValueBoost, -} from './types'; - -export const FIELD_FILTER_CUTOFF = 10; - -export const RELEVANCE_TUNING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.title', - { defaultMessage: 'Relevance Tuning' } -); - -export const UPDATE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.updateSuccess', - { - defaultMessage: 'Relevance was tuned', - } -); -export const DELETE_SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteSuccess', - { - defaultMessage: 'Relevance was reset to default values', - } -); -export const SUCCESS_CHANGES_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.successDescription', - { - defaultMessage: 'The changes will impact your results shortly.', - } -); - -export const RESET_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.resetConfirmation', - { - defaultMessage: 'Are you sure you want to restore relevance defaults?', - } -); -export const DELETE_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.messages.deleteConfirmation', - { - defaultMessage: 'Are you sure you want to delete this boost?', - } -); -export const PROXIMITY_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.proximityDropDownOptionLabel', - { - defaultMessage: 'Proximity', - } -); -export const FUNCTIONAL_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.functionalDropDownOptionLabel', - { - defaultMessage: 'Functional', - } -); -export const VALUE_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.valueDropDownOptionLabel', - { - defaultMessage: 'Value', - } -); - -export const BOOST_TYPE_TO_DISPLAY_MAP = { - [BoostType.Proximity]: PROXIMITY_DISPLAY, - [BoostType.Functional]: FUNCTIONAL_DISPLAY, - [BoostType.Value]: VALUE_DISPLAY, -}; - -export const BOOST_TYPE_TO_ICON_MAP = { - [BoostType.Value]: 'tokenNumber', - [BoostType.Functional]: 'tokenFunction', - [BoostType.Proximity]: 'tokenGeo', -}; - -const EMPTY_VALUE_BOOST: ValueBoost = { - type: BoostType.Value, - factor: 1, - value: [''], - newBoost: true, - function: undefined, - operation: undefined, -}; - -const EMPTY_FUNCTIONAL_BOOST: FunctionalBoost = { - value: undefined, - type: BoostType.Functional, - factor: 1, - newBoost: true, - function: FunctionalBoostFunction.Logarithmic, - operation: BoostOperation.Multiply, -}; - -const EMPTY_PROXIMITY_BOOST: ProximityBoost = { - value: undefined, - type: BoostType.Proximity, - factor: 1, - newBoost: true, - operation: undefined, - function: ProximityBoostFunction.Gaussian, -}; - -export const BOOST_TYPE_TO_EMPTY_BOOST = { - [BoostType.Value]: EMPTY_VALUE_BOOST, - [BoostType.Functional]: EMPTY_FUNCTIONAL_BOOST, - [BoostType.Proximity]: EMPTY_PROXIMITY_BOOST, -}; - -export const ADD_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addOperationDropDownOptionLabel', - { - defaultMessage: 'Add', - } -); - -export const MULTIPLY_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.multiplyOperationDropDownOptionLabel', - { - defaultMessage: 'Multiply', - } -); - -export const BOOST_OPERATION_DISPLAY_MAP = { - [BoostOperation.Add]: ADD_DISPLAY, - [BoostOperation.Multiply]: MULTIPLY_DISPLAY, -}; - -export const LOGARITHMIC_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.logarithmicBoostFunctionDropDownOptionLabel', - { - defaultMessage: 'Logarithmic', - } -); - -export const GAUSSIAN_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.gaussianFunctionDropDownOptionLabel', - { - defaultMessage: 'Gaussian', - } -); - -export const EXPONENTIAL_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.exponentialFunctionDropDownOptionLabel', - { - defaultMessage: 'Exponential', - } -); - -export const LINEAR_DISPLAY = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.linearFunctionDropDownOptionLabel', - { - defaultMessage: 'Linear', - } -); - -export const PROXIMITY_BOOST_FUNCTION_DISPLAY_MAP = { - [ProximityBoostFunction.Gaussian]: GAUSSIAN_DISPLAY, - [ProximityBoostFunction.Exponential]: EXPONENTIAL_DISPLAY, - [ProximityBoostFunction.Linear]: LINEAR_DISPLAY, -}; - -export const FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP = { - [FunctionalBoostFunction.Logarithmic]: LOGARITHMIC_DISPLAY, - [FunctionalBoostFunction.Exponential]: EXPONENTIAL_DISPLAY, - [FunctionalBoostFunction.Linear]: LINEAR_DISPLAY, -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts deleted file mode 100644 index 07e53d0d29282..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { RELEVANCE_TUNING_TITLE } from './constants'; -export { RelevanceTuning } from './relevance_tuning'; -export { RelevanceTuningLogic } from './relevance_tuning_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx deleted file mode 100644 index 9d5404f042712..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; -import { getPageHeaderActions } from '../../../test_helpers'; - -import { PrecisionSlider } from './components/precision_slider'; -import { RelevanceTuning } from './relevance_tuning'; - -import { RelevanceTuningCallouts } from './relevance_tuning_callouts'; -import { RelevanceTuningForm } from './relevance_tuning_form'; -import { RelevanceTuningPreview } from './relevance_tuning_preview'; - -describe('RelevanceTuning', () => { - const values = { - engineHasSchemaFields: true, - engine: { - invalidBoosts: false, - unsearchedUnconfirmedFields: false, - }, - schemaFieldsWithConflicts: [], - unsavedChanges: false, - dataLoading: false, - isPrecisionTuningEnabled: true, - }; - - const actions = { - initializeRelevanceTuning: jest.fn(), - updateSearchSettings: jest.fn(), - resetSearchSettings: jest.fn(), - }; - - const subject = () => shallow(); - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = subject(); - expect(wrapper.find(RelevanceTuningCallouts).exists()).toBe(true); - expect(wrapper.find(PrecisionSlider).exists()).toBe(true); - expect(wrapper.find(RelevanceTuningForm).exists()).toBe(true); - expect(wrapper.find(RelevanceTuningPreview).exists()).toBe(true); - }); - - it('initializes relevance tuning data', () => { - subject(); - expect(actions.initializeRelevanceTuning).toHaveBeenCalled(); - }); - - it('will prevent user from leaving the page if there are unsaved changes', () => { - setMockValues({ - ...values, - unsavedChanges: true, - }); - expect(subject().find(UnsavedChangesPrompt).prop('hasUnsavedChanges')).toBe(true); - }); - - describe('header actions', () => { - it('renders a Save button that will save the current changes', () => { - const buttons = getPageHeaderActions(subject()); - expect(buttons.children().length).toBe(2); - const saveButton = buttons.find('[data-test-subj="SaveRelevanceTuning"]'); - saveButton.simulate('click'); - expect(actions.updateSearchSettings).toHaveBeenCalled(); - }); - - it('renders a Reset button that will remove all weights and boosts', () => { - const buttons = getPageHeaderActions(subject()); - expect(buttons.children().length).toBe(2); - const resetButton = buttons.find('[data-test-subj="ResetRelevanceTuning"]'); - resetButton.simulate('click'); - expect(actions.resetSearchSettings).toHaveBeenCalled(); - }); - - it('will not render buttons if the engine has no schema', () => { - setMockValues({ - ...values, - engineHasSchemaFields: false, - }); - const buttons = getPageHeaderActions(subject()); - expect(buttons.children().length).toBe(0); - }); - }); - - describe('precision tuning', () => { - it('will not render the PrecisionSlider when precision tuning is disabled', () => { - setMockValues({ - ...values, - isPrecisionTuningEnabled: false, - }); - - expect(subject().find(PrecisionSlider).exists()).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx deleted file mode 100644 index 6c2ae8c029f8c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SAVE_BUTTON_LABEL } from '../../../shared/constants'; -import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; -import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../constants'; -import { EngineLogic, getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { EmptyState } from './components'; -import { PrecisionSlider } from './components/precision_slider'; -import { RELEVANCE_TUNING_TITLE } from './constants'; -import { RelevanceTuningCallouts } from './relevance_tuning_callouts'; -import { RelevanceTuningForm } from './relevance_tuning_form'; -import { RelevanceTuningPreview } from './relevance_tuning_preview'; - -import { RelevanceTuningLogic } from '.'; - -export const RelevanceTuning: React.FC = () => { - const { dataLoading, engineHasSchemaFields, unsavedChanges, isPrecisionTuningEnabled } = - useValues(RelevanceTuningLogic); - const { initializeRelevanceTuning, resetSearchSettings, updateSearchSettings } = - useActions(RelevanceTuningLogic); - const { isElasticsearchEngine } = useValues(EngineLogic); - - useEffect(() => { - initializeRelevanceTuning(); - }, []); - - const APP_SEARCH_MANAGED_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description', - { defaultMessage: 'Manage precision and relevance settings for your engine' } - ); - - const ELASTICSEARCH_MANAGED_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.elasticsearch.description', - { defaultMessage: 'Manage relevance settings for your engine' } - ); - - return ( - - {SAVE_BUTTON_LABEL} - , - - {RESTORE_DEFAULTS_BUTTON_LABEL} - , - ] - : [], - }} - isLoading={dataLoading} - isEmptyState={!engineHasSchemaFields} - emptyState={} - > - - - - - - - {isPrecisionTuningEnabled && ( - <> - - - - )} - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.test.tsx deleted file mode 100644 index 8384d07938678..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 '../../__mocks__/engine_logic.mock'; -import { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { RelevanceTuningCallouts } from './relevance_tuning_callouts'; - -describe('RelevanceTuningCallouts', () => { - const values = { - engineHasSchemaFields: true, - engine: { - invalidBoosts: false, - unsearchedUnconfirmedFields: false, - }, - schemaFieldsWithConflicts: [], - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - const subject = () => shallow(); - - it('renders', () => { - const wrapper = subject(); - expect(wrapper.find('[data-test-subj="RelevanceTuningInvalidBoostsCallout"]').exists()).toBe( - false - ); - expect(wrapper.find('[data-test-subj="RelevanceTuningUnsearchedFieldsCallout"]').exists()).toBe( - false - ); - expect(subject().find('[data-test-subj="SchemaConflictsCallout"]').exists()).toBe(false); - }); - - it('shows a message when there are invalid boosts', () => { - // An invalid boost would be if a user creats a functional boost on a number field, then that - // field later changes to text. At this point, the boost still exists but is invalid for - // a text field. - setMockValues({ - ...values, - engine: { - invalidBoosts: true, - unsearchedUnconfirmedFields: false, - }, - }); - expect(subject().find('[data-test-subj="RelevanceTuningInvalidBoostsCallout"]').exists()).toBe( - true - ); - }); - - it('shows a message when there are unconfirmed fields', () => { - // An invalid boost would be if a user creats a functional boost on a number field, then that - // field later changes to text. At this point, the boost still exists but is invalid for - // a text field. - setMockValues({ - ...values, - engine: { - invalidBoosts: false, - unsearchedUnconfirmedFields: true, - }, - }); - expect( - subject().find('[data-test-subj="RelevanceTuningUnsearchedFieldsCallout"]').exists() - ).toBe(true); - }); - - it('shows a message when there are schema field conflicts', () => { - // Schema conflicts occur when a meta engine has fields in source engines with have differing types, - // hence relevance tuning cannot be applied as we don't know the actual type - setMockValues({ - ...values, - schemaFieldsWithConflicts: ['fe', 'fi', 'fo'], - }); - expect(subject().find('[data-test-subj="SchemaConflictsCallout"]').exists()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx deleted file mode 100644 index ef0bea39439c5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiCallOut, EuiLink } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../shared/doc_links'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; -import { ENGINE_SCHEMA_PATH } from '../../routes'; -import { EngineLogic, generateEnginePath } from '../engine'; - -import { RelevanceTuningLogic } from '.'; - -export const RelevanceTuningCallouts: React.FC = () => { - const { schemaFieldsWithConflicts } = useValues(RelevanceTuningLogic); - const { - engine: { invalidBoosts, unsearchedUnconfirmedFields }, - } = useValues(EngineLogic); - - const schemaFieldsWithConflictsCount = schemaFieldsWithConflicts.length; - - const invalidBoostsCallout = () => ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.invalidBoostsErrorMessage', - { - defaultMessage: - 'One or more of your boosts is no longer valid, possibly due to a schema type change. Delete any old or invalid boosts to dismiss this alert.', - } - )} - - ); - - const unsearchedUnconfirmedFieldsCallout = () => ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.schemaFieldsLinkLabel', - { - defaultMessage: 'schema fields', - } - )} - - ), - }} - /> - - ); - - const schemaFieldsWithConflictsCallout = () => ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.whatsThisLinkLabel', - { - defaultMessage: "What's this?", - } - )} - - ), - }} - /> - - ); - - return ( - <> - {invalidBoosts && invalidBoostsCallout()} - {unsearchedUnconfirmedFields && unsearchedUnconfirmedFieldsCallout()} - {schemaFieldsWithConflictsCount > 0 && schemaFieldsWithConflictsCallout()} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts deleted file mode 100644 index 89e344860b2eb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { RelevanceTuningForm } from './relevance_tuning_form'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss deleted file mode 100644 index 065effef9dded..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss +++ /dev/null @@ -1,25 +0,0 @@ -.relevanceTuningForm { - &__item { - width: 100%; - margin-left: $euiSizeS; - } - - &__panel + &__panel { - margin-top: $euiSizeM; - } - - &__panel .euiAccordion__button { - &:hover, - &:focus { - text-decoration: none; - h3 { - text-decoration: underline; - } - } - } - - .relevanceTuningAccordionItem { - border-top: $euiBorderThin; - border-radius: 0; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx deleted file mode 100644 index 6c6a4a7c05667..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.test.tsx +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiFieldSearch } from '@elastic/eui'; - -import { BoostType } from '../types'; - -import { RelevanceTuningForm } from './relevance_tuning_form'; -import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; - -describe('RelevanceTuningForm', () => { - const values = { - filterInputValue: '', - schemaFields: ['foo', 'bar', 'baz'], - filteredSchemaFields: ['foo', 'bar'], - filteredSchemaFieldsWithConflicts: [], - schema: { - foo: { type: 'text' }, - bar: { type: 'number' }, - }, - searchSettings: { - boosts: { - foo: [ - { - factor: 2, - type: BoostType.Value, - value: [], - }, - ], - }, - search_fields: { - bar: { - weight: 1, - }, - }, - }, - }; - const actions = { - setFilterValue: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - describe('fields', () => { - let wrapper: ShallowWrapper; - let relevantTuningItems: any; - - beforeAll(() => { - setMockValues(values); - - wrapper = shallow(); - relevantTuningItems = wrapper.find(RelevanceTuningItemContent); - }); - - it('renders a list of fields that may or may not have been filterd by user input', () => { - // The length is 2 because we're only pulling values from `filteredSchemaFields`, which - // is the list of schema fields that has been filtered by user input down to 2 - expect(relevantTuningItems.length).toBe(2); - }); - - it('will pass the schema field name in the "name" prop of each list item', () => { - expect(relevantTuningItems.at(0).prop('name')).toBe('foo'); - expect(relevantTuningItems.at(1).prop('name')).toBe('bar'); - }); - - it('will pass the schema type of the field in the "type" prop of each list item', () => { - expect(relevantTuningItems.at(0).prop('type')).toBe('text'); - expect(relevantTuningItems.at(1).prop('type')).toBe('number'); - }); - - it('will pass a list of boosts in the "boosts" field of each list item, or undefined if none exist', () => { - expect(relevantTuningItems.at(0).prop('boosts')).toEqual([ - { - factor: 2, - type: BoostType.Value, - value: [], - }, - ]); - expect(relevantTuningItems.at(1).prop('boosts')).toBeUndefined(); - }); - - it('will pass the search_field configuration for the field in the "field" prop of each list item, or undefined if none exists', () => { - expect(relevantTuningItems.at(0).prop('field')).toBeUndefined(); - expect(relevantTuningItems.at(1).prop('field')).toEqual({ - weight: 1, - }); - }); - - it('wont show disabled fields section if there are no fields with schema conflicts', () => { - expect(wrapper.find('[data-test-subj="DisabledFieldsSection"]').exists()).toBe(false); - }); - }); - - it('will show a disabled fields section if there are fields that have schema conflicts', () => { - // There will only ever be fields with schema conflicts if this is the relevance tuning - // page for a meta engine - setMockValues({ - ...values, - filteredSchemaFieldsWithConflicts: ['fe', 'fi', 'fo'], - }); - - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="DisabledFieldsSection"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="DisabledField"]').map((f) => f.text())).toEqual([ - 'fe', - 'fi', - 'fo', - ]); - }); - - describe('field filtering', () => { - let searchField: ShallowWrapper; - - beforeEach(() => { - setMockValues({ - ...values, - filterInputValue: 'test', - schemaFields: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'], - }); - const wrapper = shallow(); - searchField = wrapper.find(EuiFieldSearch); - }); - - it('renders an input box for filtering the field list in case there is a large quantity of fields', () => { - expect(searchField.exists()).toBe(true); - }); - - it('initializes the input box with the user input value stored in state', () => { - expect(searchField.prop('value')).toBe('test'); - }); - - it('updates the user input value stored in state whenever the input box value changes', () => { - searchField.simulate('change', { - target: { - value: 'new value', - }, - }); - - expect(actions.setFilterValue).toHaveBeenCalledWith('new value'); - }); - - it('will not render a field filter if there are 10 or less fields', () => { - setMockValues({ - ...values, - schemaFields: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], - }); - const wrapper = shallow(); - expect(wrapper.find(EuiFieldSearch).exists()).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx deleted file mode 100644 index 786fb4bc778d2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 { - EuiTitle, - EuiFieldSearch, - EuiSpacer, - EuiAccordion, - EuiPanel, - EuiHealth, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { FIELD_FILTER_CUTOFF } from '../constants'; -import { RelevanceTuningLogic } from '../relevance_tuning_logic'; - -import { RelevanceTuningItem } from './relevance_tuning_item'; -import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; - -import './relevance_tuning_form.scss'; - -export const RelevanceTuningForm: React.FC = () => { - const { - filterInputValue, - schemaFields, - filteredSchemaFields, - filteredSchemaFieldsWithConflicts, - schema, - searchSettings, - } = useValues(RelevanceTuningLogic); - const { setFilterValue } = useActions(RelevanceTuningLogic); - - return ( -
    -
    - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title', - { - defaultMessage: 'Manage fields', - } - )} -

    -
    - - {schemaFields.length > FIELD_FILTER_CUTOFF && ( - <> - setFilterValue(e.target.value)} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder', - { - defaultMessage: 'Filter {schemaFieldsLength} fields...', - values: { - schemaFieldsLength: schemaFields.length, - }, - } - )} - fullWidth - /> - - - )} - {filteredSchemaFields.map((fieldName) => ( - - - } - paddingSize="s" - > - - - - ))} - - {filteredSchemaFieldsWithConflicts.length > 0 && ( - <> - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFields.title', - { - defaultMessage: 'Disabled fields', - } - )} -

    -
    - - {filteredSchemaFieldsWithConflicts.map((fieldName) => ( - - -

    {fieldName}

    -
    - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.disabledFieldsExplanationMessage', - { - defaultMessage: 'Inactive due to field-type conflict', - } - )} - -
    - ))} - - )} - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx deleted file mode 100644 index 1d813cfc8f6a0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 { SchemaType } from '../../../../shared/schema/types'; - -import { BoostIcon, ValueBadge } from '../components'; -import { Boost, BoostType, SearchField } from '../types'; - -import { RelevanceTuningItem } from './relevance_tuning_item'; - -describe('RelevanceTuningItem', () => { - const props = { - name: 'foo', - type: SchemaType.Text, - boosts: [ - { - factor: 2, - type: BoostType.Value, - value: [''], - }, - ], - field: { - weight: 1, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('boosts prop', () => { - const renderComponentWithBoostsConfig = (boosts?: Boost[]) => { - return shallow( - - ); - }; - - describe('when there are boosts for this field', () => { - it('renders an icon for each boost that is applied', () => { - const wrapper = renderComponentWithBoostsConfig([ - { - factor: 2, - type: BoostType.Value, - value: [''], - }, - { - factor: 3, - type: BoostType.Proximity, - }, - ]); - expect(wrapper.find(BoostIcon).length).toBe(2); - expect(wrapper.find(BoostIcon).map((euiToken) => euiToken.prop('type'))).toEqual([ - BoostType.Value, - BoostType.Proximity, - ]); - }); - }); - - describe('when there are no boosts for this field', () => { - const wrapper = renderComponentWithBoostsConfig(); - - it('renders an icon for each boost that is applied', () => { - expect(wrapper.find(BoostIcon).length).toBe(0); - }); - }); - }); - - describe('field prop', () => { - const renderComponentWithFieldConfig = (field?: SearchField) => { - return shallow( - - ); - }; - - describe('when weight is set to any positive number', () => { - const wrapper = renderComponentWithFieldConfig({ - weight: 1, - }); - - it('will show the weight with an "enabled" style', () => { - const valueBadge = wrapper.find(ValueBadge); - expect(valueBadge.dive().text()).toContain('1'); - expect(valueBadge.prop('disabled')).toBe(false); - }); - }); - - describe('when weight set to "0", which means this field will not be searched', () => { - const wrapper = renderComponentWithFieldConfig({ - weight: 0, - }); - - it('will show 0 with a "disabled" style', () => { - const valueBadge = wrapper.find(ValueBadge); - expect(valueBadge.dive().text()).toContain('0'); - expect(valueBadge.prop('disabled')).toBe(true); - }); - }); - - describe('when there is no weight set, which means this field will not be searched', () => { - const wrapper = renderComponentWithFieldConfig(); - - it('will show "0" with a "disabled" style', () => { - const valueBadge = wrapper.find(ValueBadge); - expect(valueBadge.dive().text()).toContain('0'); - expect(valueBadge.prop('disabled')).toBe(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx deleted file mode 100644 index f6f5135792141..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { EuiText, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiTextColor, EuiIcon } from '@elastic/eui'; - -import { SchemaType } from '../../../../shared/schema/types'; - -import { BoostIcon, ValueBadge } from '../components'; -import { Boost, SearchField } from '../types'; - -interface Props { - name: string; - type: SchemaType; - boosts?: Boost[]; - field?: SearchField; -} - -export const RelevanceTuningItem: React.FC = ({ name, type, boosts = [], field }) => { - return ( - - - -

    {name}

    -
    - - {type} - -
    - - - {boosts.map((boost, index) => ( - - - - ))} - - - - {!!field ? field.weight : 0} - - - - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts deleted file mode 100644 index dc5b95320d4db..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { RelevanceTuningItemContent } from './relevance_tuning_item_content'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx deleted file mode 100644 index 9b3003a192107..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { SchemaType } from '../../../../../shared/schema/types'; -import { BoostType } from '../../types'; - -import { RelevanceTuningItemContent } from './relevance_tuning_item_content'; -import { TextSearchToggle } from './text_search_toggle'; -import { WeightSlider } from './weight_slider'; - -describe('RelevanceTuningItemContent', () => { - const props = { - name: 'foo', - type: SchemaType.Text, - boosts: [ - { - factor: 2, - type: BoostType.Value, - value: [''], - }, - ], - field: { - weight: 1, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - - const textSearchToggle = wrapper.find(TextSearchToggle); - expect(textSearchToggle.exists()).toBe(true); - expect(textSearchToggle.prop('name')).toBe(props.name); - expect(textSearchToggle.prop('type')).toBe(props.type); - expect(textSearchToggle.prop('field')).toBe(props.field); - - const weightSlider = wrapper.find(WeightSlider); - expect(weightSlider.exists()).toBe(true); - expect(weightSlider.prop('name')).toBe(props.name); - expect(weightSlider.prop('field')).toBe(props.field); - }); - - it('will not render a WeightSlider if the field prop is empty', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(WeightSlider).exists()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx deleted file mode 100644 index 18bce47b18ae4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { EuiPanel } from '@elastic/eui'; - -import { SchemaType } from '../../../../../shared/schema/types'; - -import { Boosts } from '../../boosts'; -import { Boost, SearchField } from '../../types'; - -import { TextSearchToggle } from './text_search_toggle'; -import { WeightSlider } from './weight_slider'; - -interface Props { - name: string; - type: SchemaType; - boosts?: Boost[]; - field?: SearchField; -} - -export const RelevanceTuningItemContent: React.FC = ({ name, type, boosts, field }) => { - return ( - <> - - - {field && } - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx deleted file mode 100644 index 85cf4af7a7b76..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiSwitch } from '@elastic/eui'; - -import { SchemaType } from '../../../../../shared/schema/types'; - -import { TextSearchToggle } from './text_search_toggle'; - -describe('TextSearchToggle', () => { - const actions = { - toggleSearchField: jest.fn(), - }; - - beforeAll(() => { - setMockActions(actions); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('typical render', () => { - let wrapper: ShallowWrapper; - - const props = { - name: 'foo', - type: SchemaType.Text, - field: { - weight: 1, - }, - }; - - beforeAll(() => { - wrapper = shallow(); - }); - - it('renders a toggle button', () => { - expect(wrapper.find(EuiSwitch).exists()).toBe(true); - }); - - it('shows the toggle button as checked if any value was passed in the "field" prop', () => { - expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true); - }); - - it('shows the toggle as enabled if "text" was passed in the "type" prop', () => { - expect(wrapper.find(EuiSwitch).prop('disabled')).toBe(false); - }); - - it('shows a relevant label if "text" was passed in the "type" prop', () => { - expect(wrapper.find(EuiSwitch).prop('label')).toBe('Search this field'); - }); - - it('will update toggled state when clicked', () => { - wrapper.find(EuiSwitch).simulate('change'); - expect(actions.toggleSearchField).toHaveBeenCalledWith('foo', true); - }); - }); - - describe('when a non-"text" type is passed in the "type" prop', () => { - let wrapper: ShallowWrapper; - - const props = { - name: 'foo', - type: SchemaType.Number, - field: { - weight: 1, - }, - }; - - beforeAll(() => { - wrapper = shallow(); - }); - - it('shows the toggle button as disabled', () => { - expect(wrapper.find(EuiSwitch).prop('checked')).toBe(true); - }); - - it('shows a relevant label', () => { - expect(wrapper.find(EuiSwitch).prop('label')).toBe( - 'Search can only be enabled on text fields' - ); - }); - - it('will not update state when the clicked', () => { - wrapper.find(EuiSwitch).simulate('change'); - expect(actions.toggleSearchField).not.toHaveBeenCalled(); - }); - }); - - describe('when no field prop is passed', () => { - let wrapper: ShallowWrapper; - - const props = { - name: 'foo', - type: SchemaType.Text, - }; - - beforeAll(() => { - wrapper = shallow(); - }); - - it('shows the toggle button as unchecked', () => { - expect(wrapper.find(EuiSwitch).prop('checked')).toBe(false); - }); - - it('will update toggled state when clicked', () => { - wrapper.find(EuiSwitch).simulate('change'); - expect(actions.toggleSearchField).toHaveBeenCalledWith('foo', false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx deleted file mode 100644 index 937e4dc9f2daa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/text_search_toggle.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiFormRow, EuiSwitch } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { SchemaType } from '../../../../../shared/schema/types'; - -import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; -import { SearchField } from '../../types'; - -interface Props { - name: string; - type: SchemaType; - field?: SearchField; -} - -export const TextSearchToggle: React.FC = ({ name, type, field }) => { - const { toggleSearchField } = useActions(RelevanceTuningLogic); - const isText = type === SchemaType.Text; - - return ( - - isText && toggleSearchField(name, !!field)} - checked={!!field} - disabled={!isText} - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx deleted file mode 100644 index 49c02ef8e94f2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiRange } from '@elastic/eui'; - -import { WeightSlider } from './weight_slider'; - -describe('WeightSlider', () => { - let wrapper: ShallowWrapper; - - const actions = { - updateFieldWeight: jest.fn(), - }; - - beforeAll(() => { - setMockActions(actions); - wrapper = shallow( - - ); - }); - - it('renders with an initial value set', () => { - expect(wrapper.find(EuiRange).exists()).toBe(true); - expect(wrapper.find(EuiRange).prop('value')).toBe(2.2); - }); - - it('updates field weight in state when the value changes', () => { - wrapper.find(EuiRange).simulate('change', { - target: { - value: '1.3', - }, - }); - expect(actions.updateFieldWeight).toHaveBeenCalledWith('foo', 1.3); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx deleted file mode 100644 index 02e83b81b2cb1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/weight_slider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiFormRow, EuiRange } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; -import { SearchField } from '../../types'; - -interface Props { - name: string; - field: SearchField; -} - -export const WeightSlider: React.FC = ({ name, field }) => { - const { updateFieldWeight } = useActions(RelevanceTuningLogic); - - return ( - - - updateFieldWeight( - name, - parseFloat((e as React.ChangeEvent).target.value) - ) - } - showInput - compressed - fullWidth - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts deleted file mode 100644 index 506e983cbe052..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ /dev/null @@ -1,1226 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; -import { mockEngineValues, mockEngineActions } from '../../__mocks__'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { Boost, BoostOperation, BoostType, FunctionalBoostFunction } from './types'; - -import { RelevanceTuningLogic } from '.'; - -describe('RelevanceTuningLogic', () => { - const { mount } = new LogicMounter(RelevanceTuningLogic); - - const searchSettings = { - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - value: [], - }, - ], - }, - search_fields: {}, - precision: 10, - precision_enabled: true, - }; - const schema = {}; - const schemaConflicts = {}; - const relevanceTuningProps = { - searchSettings, - schema, - schemaConflicts, - }; - const searchResults = [ - { - id: { - raw: '1', - }, - _meta: { - id: '1', - score: 100, - engine: 'my-engine', - }, - }, - ]; - - const DEFAULT_VALUES = { - dataLoading: true, - schema: {}, - schemaConflicts: {}, - searchSettings: { - boosts: {}, - search_fields: {}, - precision: 2, - precision_enabled: false, - }, - unsavedChanges: false, - filterInputValue: '', - query: '', - resultsLoading: false, - searchResults: null, - engineHasSchemaFields: false, - schemaFields: [], - schemaFieldsWithConflicts: [], - filteredSchemaFields: [], - filteredSchemaFieldsWithConflicts: [], - isPrecisionTuningEnabled: false, - }; - - const DEFAULT_VALUES_WITH_PRECISION_TUNING = { - ...DEFAULT_VALUES, - searchSettings: { - ...DEFAULT_VALUES.searchSettings, - precision_enabled: true, - }, - isPrecisionTuningEnabled: true, - }; - - beforeEach(() => { - jest.clearAllMocks(); - mockEngineValues.engineName = 'test-engine'; - mockEngineValues.engine.invalidBoosts = false; - mockEngineValues.engine.unsearchedUnconfirmedFields = false; - }); - - it('has expected default values', () => { - mount(); - expect(RelevanceTuningLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onInitializeRelevanceTuning', () => { - it('should set searchSettings, schema, & schemaConflicts from the API response, and set dataLoading to false', () => { - mount({ - dataLoading: true, - }); - RelevanceTuningLogic.actions.onInitializeRelevanceTuning(relevanceTuningProps); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_PRECISION_TUNING, - searchSettings, - schema, - dataLoading: false, - schemaConflicts, - }); - }); - - it('should default schemaConflicts if it is not passed', () => { - mount({ - dataLoading: true, - }); - RelevanceTuningLogic.actions.onInitializeRelevanceTuning({ - searchSettings, - schema, - }); - - expect(RelevanceTuningLogic.values.schemaConflicts).toEqual({}); - }); - }); - - describe('setSearchSettings', () => { - it('should set setSearchSettings and set unsavedChanges to true', () => { - mount({ - unsavedChanges: false, - }); - RelevanceTuningLogic.actions.setSearchSettings(searchSettings); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_PRECISION_TUNING, - searchSettings, - unsavedChanges: true, - }); - }); - }); - - describe('setFilterValue', () => { - it('should set filterInputValue', () => { - mount(); - RelevanceTuningLogic.actions.setFilterValue('foo'); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - filterInputValue: 'foo', - }); - }); - }); - - describe('setSearchQuery', () => { - it('should set query', () => { - mount(); - RelevanceTuningLogic.actions.setSearchQuery('foo'); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - query: 'foo', - }); - }); - }); - - describe('setSearchResults', () => { - it('should set searchResults and set resultLoading to false', () => { - mount({ - resultsLoading: true, - }); - RelevanceTuningLogic.actions.setSearchResults(searchResults); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - searchResults, - resultsLoading: false, - }); - }); - }); - - describe('setResultsLoading', () => { - it('should set resultsLoading', () => { - mount({ - resultsLoading: false, - }); - RelevanceTuningLogic.actions.setResultsLoading(true); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - resultsLoading: true, - }); - }); - }); - - describe('clearSearchResults', () => { - it('should set searchResults', () => { - mount({ - searchResults: [{}], - }); - RelevanceTuningLogic.actions.clearSearchResults(); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - searchResults: null, - }); - }); - }); - - describe('resetSearchSettingsState', () => { - it('should set dataLoading', () => { - mount({ - dataLoading: false, - }); - RelevanceTuningLogic.actions.resetSearchSettingsState(); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - }); - - describe('setSearchSettingsResponse', () => { - it('should set searchSettings state and unsavedChanges to false', () => { - mount({ - unsavedChanges: true, - }); - RelevanceTuningLogic.actions.setSearchSettingsResponse(searchSettings); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES_WITH_PRECISION_TUNING, - searchSettings, - unsavedChanges: false, - }); - }); - }); - - describe('setPrecision', () => { - it('should set precision inside search settings and set unsavedChanges to true', () => { - mount(); - RelevanceTuningLogic.actions.setPrecision(9); - - expect(RelevanceTuningLogic.values).toEqual({ - ...DEFAULT_VALUES, - searchSettings: { - ...DEFAULT_VALUES.searchSettings, - precision: 9, - }, - unsavedChanges: true, - }); - }); - }); - }); - - describe('listeners', () => { - const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast, clearFlashMessages } = mockFlashMessageHelpers; - let scrollToSpy: jest.SpyInstance; - let confirmSpy: jest.SpyInstance; - - const searchSettingsWithBoost = (boost: Boost) => ({ - ...searchSettings, - boosts: { - foo: [ - { - factor: 1, - type: BoostType.Functional, - }, - boost, - ], - }, - }); - - beforeAll(() => { - scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation(() => true); - confirmSpy = jest.spyOn(window, 'confirm'); - }); - - afterAll(() => { - scrollToSpy.mockRestore(); - confirmSpy.mockRestore(); - }); - - describe('initializeRelevanceTuning', () => { - it('should make an API call and set state based on the normalized response', async () => { - mount(); - http.get.mockReturnValueOnce( - Promise.resolve({ - ...relevanceTuningProps, - searchSettings: { - ...relevanceTuningProps.searchSettings, - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - value: 5, - }, - ], - }, - }, - }) - ); - jest.spyOn(RelevanceTuningLogic.actions, 'onInitializeRelevanceTuning'); - - RelevanceTuningLogic.actions.initializeRelevanceTuning(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/search_settings/details' - ); - expect(RelevanceTuningLogic.actions.onInitializeRelevanceTuning).toHaveBeenCalledWith({ - ...relevanceTuningProps, - searchSettings: { - ...relevanceTuningProps.searchSettings, - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - value: ['5'], - }, - ], - }, - }, - }); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - RelevanceTuningLogic.actions.initializeRelevanceTuning(); - }); - }); - - describe('getSearchResults', () => { - beforeAll(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - - it('should make an API call, set state based on the response, and clear flash messages', async () => { - const searchSettingsWithNewBoostProp = { - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - newBoost: true, // This should be deleted before sent to the server - value: ['test'], - }, - ], - }, - search_fields: { - bar: { - weight: 1, - }, - }, - precision: 7, - precision_enabled: true, - }; - - const { ['precision_enabled']: precisionEnabled, ...searchSettingsWithoutNewBoostProp } = { - ...searchSettingsWithNewBoostProp, - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - value: ['test'], - }, - ], - }, - precision: 7, - }; - - mount({ - query: 'foo', - searchSettings: searchSettingsWithNewBoostProp, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchResults'); - jest.spyOn(RelevanceTuningLogic.actions, 'setResultsLoading'); - http.post.mockReturnValueOnce( - Promise.resolve({ - results: searchResults, - }) - ); - - RelevanceTuningLogic.actions.getSearchResults(); - jest.runAllTimers(); - await nextTick(); - - expect(RelevanceTuningLogic.actions.setResultsLoading).toHaveBeenCalledWith(true); - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines/test-engine/search', { - body: JSON.stringify(searchSettingsWithoutNewBoostProp), - query: { - query: 'foo', - }, - }); - expect(RelevanceTuningLogic.actions.setSearchResults).toHaveBeenCalledWith(searchResults); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it("won't send boosts or search_fields on the API call if there are none", async () => { - mount({ - query: 'foo', - searchSettings: { - searchField: {}, - boosts: {}, - precision: 7, - precision_enabled: true, - }, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchResults'); - http.post.mockReturnValueOnce( - Promise.resolve({ - results: searchResults, - }) - ); - - RelevanceTuningLogic.actions.getSearchResults(); - - jest.runAllTimers(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines/test-engine/search', { - body: JSON.stringify({ precision: 7 }), - query: { - query: 'foo', - }, - }); - }); - - it("won't send precision on the API call if it is not enabled", async () => { - mount({ - query: 'foo', - searchSettings: { - searchField: {}, - boosts: {}, - precision: 7, - precision_enabled: false, - }, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchResults'); - http.post.mockReturnValueOnce( - Promise.resolve({ - results: searchResults, - }) - ); - - RelevanceTuningLogic.actions.getSearchResults(); - - jest.runAllTimers(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines/test-engine/search', { - body: '{}', - query: { - query: 'foo', - }, - }); - }); - - it('will call clearSearchResults if there is no query', async () => { - mount({ - query: '', - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchResults'); - jest.spyOn(RelevanceTuningLogic.actions, 'setResultsLoading'); - jest.spyOn(RelevanceTuningLogic.actions, 'clearSearchResults'); - - RelevanceTuningLogic.actions.getSearchResults(); - jest.runAllTimers(); - await nextTick(); - - expect(RelevanceTuningLogic.actions.clearSearchResults).toHaveBeenCalled(); - expect(RelevanceTuningLogic.actions.setSearchResults).not.toHaveBeenCalled(); - expect(RelevanceTuningLogic.actions.setResultsLoading).not.toHaveBeenCalled(); - }); - - it('handles errors', async () => { - mount({ - query: 'foo', - }); - http.post.mockReturnValueOnce(Promise.reject('error')); - RelevanceTuningLogic.actions.getSearchResults(); - - jest.runAllTimers(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - - describe('setSearchSettings', () => { - it('updates search results whenever search settings are changed', () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'getSearchResults'); - - RelevanceTuningLogic.actions.setSearchSettings(searchSettings); - - expect(RelevanceTuningLogic.actions.getSearchResults).toHaveBeenCalled(); - }); - }); - - describe('onSearchSettingsSuccess', () => { - it('should save the response, trigger a new search, and then scroll to the top', () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettingsResponse'); - jest.spyOn(RelevanceTuningLogic.actions, 'getSearchResults'); - - RelevanceTuningLogic.actions.onSearchSettingsSuccess(searchSettings); - - expect(RelevanceTuningLogic.actions.setSearchSettingsResponse).toHaveBeenCalledWith( - searchSettings - ); - expect(RelevanceTuningLogic.actions.getSearchResults).toHaveBeenCalled(); - expect(scrollToSpy).toHaveBeenCalledWith(0, 0); - }); - }); - - describe('onSearchSettingsError', () => { - it('scrolls to the top', () => { - mount(); - RelevanceTuningLogic.actions.onSearchSettingsError(); - expect(scrollToSpy).toHaveBeenCalledWith(0, 0); - }); - }); - - describe('updateSearchSettings', () => { - it('calls an API endpoint and handles success response', async () => { - const searchSettingsWithNewBoostProp = { - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - newBoost: true, // This should be deleted before sent to the server - value: [''], - }, - ], - }, - precision: 5, - precision_enabled: true, - }; - - const searchSettingsWithoutNewBoostProp = { - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - value: [''], - }, - ], - }, - precision: 5, - }; - mount({ - searchSettings: searchSettingsWithNewBoostProp, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'onSearchSettingsSuccess'); - http.put.mockReturnValueOnce(Promise.resolve(searchSettingsWithoutNewBoostProp)); - - RelevanceTuningLogic.actions.updateSearchSettings(); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/search_settings', - { - body: JSON.stringify(searchSettingsWithoutNewBoostProp), - } - ); - expect(flashSuccessToast).toHaveBeenCalledWith('Relevance was tuned', { - text: 'The changes will impact your results shortly.', - }); - expect(RelevanceTuningLogic.actions.onSearchSettingsSuccess).toHaveBeenCalledWith( - searchSettingsWithoutNewBoostProp - ); - }); - - it('handles errors', async () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'onSearchSettingsError'); - http.put.mockReturnValueOnce(Promise.reject('error')); - - RelevanceTuningLogic.actions.updateSearchSettings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - expect(RelevanceTuningLogic.actions.onSearchSettingsError).toHaveBeenCalled(); - }); - - it('will re-fetch the current engine after settings are updated if there were invalid boosts', async () => { - mockEngineValues.engine.invalidBoosts = true; - mount({}); - http.put.mockReturnValueOnce(Promise.resolve(searchSettings)); - - RelevanceTuningLogic.actions.updateSearchSettings(); - await nextTick(); - - expect(mockEngineActions.initializeEngine).toHaveBeenCalled(); - }); - - it('will re-fetch the current engine after settings are updated if there were unconfirmed search fields', async () => { - mockEngineValues.engine.unsearchedUnconfirmedFields = true; - mount({}); - http.put.mockReturnValueOnce(Promise.resolve(searchSettings)); - - RelevanceTuningLogic.actions.updateSearchSettings(); - await nextTick(); - - expect(mockEngineActions.initializeEngine).toHaveBeenCalled(); - }); - }); - - describe('resetSearchSettings', () => { - it('calls and API endpoint, shows a success message, and saves the response', async () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'onSearchSettingsSuccess'); - confirmSpy.mockImplementation(() => true); - http.post.mockReturnValueOnce(Promise.resolve(searchSettings)); - - RelevanceTuningLogic.actions.resetSearchSettings(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/search_settings/reset' - ); - expect(flashSuccessToast).toHaveBeenCalledWith('Relevance was reset to default values', { - text: 'The changes will impact your results shortly.', - }); - expect(RelevanceTuningLogic.actions.onSearchSettingsSuccess).toHaveBeenCalledWith( - searchSettings - ); - }); - - it('does nothing if the user does not confirm', async () => { - mount(); - confirmSpy.mockImplementation(() => false); - - RelevanceTuningLogic.actions.resetSearchSettings(); - await nextTick(); - - expect(http.post).not.toHaveBeenCalled(); - }); - - it('handles errors', async () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'onSearchSettingsError'); - confirmSpy.mockImplementation(() => true); - http.post.mockReturnValueOnce(Promise.reject('error')); - - RelevanceTuningLogic.actions.resetSearchSettings(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - expect(RelevanceTuningLogic.actions.onSearchSettingsError).toHaveBeenCalled(); - }); - }); - - describe('toggleSearchField', () => { - it('updates search weight to 1 in search fields when enabling', () => { - mount({ - searchSettings: { - ...searchSettings, - search_fields: { - bar: { - weight: 1, - }, - }, - }, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.toggleSearchField('foo', false); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - search_fields: { - bar: { - weight: 1, - }, - foo: { - weight: 1, - }, - }, - }); - }); - - it('removes fields from search fields when disabling', () => { - mount({ - searchSettings: { - ...searchSettings, - search_fields: { - bar: { - weight: 1, - }, - }, - }, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.toggleSearchField('bar', true); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - }); - }); - }); - - describe('updateFieldWeight', () => { - it('updates the search weight in search fields', () => { - mount({ - searchSettings, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateFieldWeight('foo', 3); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - search_fields: { - foo: { - weight: 3, - }, - }, - }); - }); - - it('will round decimal numbers', () => { - mount({ - searchSettings, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateFieldWeight('foo', 3.9393939); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - search_fields: { - foo: { - weight: 3.9, - }, - }, - }); - }); - }); - - describe('addBoost', () => { - it('adds a boost of given type for the given field', () => { - mount({ - searchSettings: { - ...searchSettings, - boosts: { - foo: [ - { - factor: 2, - type: BoostType.Value, - value: [''], - }, - ], - }, - }, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.addBoost('foo', BoostType.Functional); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - boosts: { - foo: [ - { - factor: 2, - type: BoostType.Value, - value: [''], - }, - { - factor: 1, - newBoost: true, - type: BoostType.Functional, - function: 'logarithmic', - operation: 'multiply', - value: undefined, - }, - ], - }, - }); - }); - - it('works even if there are no boosts yet', () => { - mount({ - searchSettings: { - ...searchSettings, - boosts: {}, - }, - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.addBoost('foo', BoostType.Functional); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - boosts: { - foo: [ - { - factor: 1, - newBoost: true, - type: BoostType.Functional, - function: 'logarithmic', - operation: 'multiply', - value: undefined, - }, - ], - }, - }); - }); - }); - - describe('deleteBoost', () => { - it('deletes the boost with the given name and index', () => { - mount({ - searchSettings: { - ...searchSettings, - boosts: { - foo: [ - { - factor: 1, - type: BoostType.Functional, - }, - { - factor: 2, - type: BoostType.Value, - value: [''], - }, - ], - }, - }, - }); - confirmSpy.mockImplementation(() => true); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.deleteBoost('foo', 1); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - boosts: { - foo: [ - { - factor: 1, - type: BoostType.Functional, - }, - ], - }, - }); - }); - - it('will delete they field key in boosts if this is the last boost or that field', () => { - mount({ - searchSettings: { - ...searchSettings, - boosts: { - foo: [ - { - factor: 1, - type: BoostType.Functional, - }, - ], - }, - }, - }); - confirmSpy.mockImplementation(() => true); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.deleteBoost('foo', 0); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith({ - ...searchSettings, - boosts: {}, - }); - }); - - it('will do nothing if the user does not confirm', () => { - mount({ - searchSettings: { - ...searchSettings, - boosts: { - foo: [ - { - factor: 1, - type: BoostType.Functional, - }, - ], - }, - }, - }); - confirmSpy.mockImplementation(() => false); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.deleteBoost('foo', 0); - - expect(RelevanceTuningLogic.actions.setSearchSettings).not.toHaveBeenCalled(); - }); - }); - - describe('updateBoostFactor', () => { - it('updates the boost factor of the target boost', () => { - mount({ - searchSettings: searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - }), - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateBoostFactor('foo', 1, 5); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( - searchSettingsWithBoost({ - factor: 5, - type: BoostType.Functional, - }) - ); - }); - - it('will round decimal numbers', () => { - mount({ - searchSettings: searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - }), - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateBoostFactor('foo', 1, 5.293191); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( - searchSettingsWithBoost({ - factor: 5.3, - type: BoostType.Functional, - }) - ); - }); - }); - - describe('updateBoostValue', () => { - it('will update the boost value and update search results', () => { - mount({ - searchSettings: searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - value: ['a', 'b', 'c'], - }), - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateBoostValue('foo', 1, ['x', 'y', 'z']); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( - searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - value: ['x', 'y', 'z'], - }) - ); - }); - }); - - describe('updateBoostCenter', () => { - it('will parse the provided provided value and set the center to that parsed value', () => { - mount({ - schema: { - foo: { type: 'number', capabilities: { boost: true } }, - }, - searchSettings: searchSettingsWithBoost({ - factor: 1, - type: BoostType.Proximity, - center: 1, - }), - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateBoostCenter('foo', 1, '4'); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( - searchSettingsWithBoost({ - factor: 1, - type: BoostType.Proximity, - center: 4, - }) - ); - }); - }); - - describe('updateBoostSelectOption', () => { - it('will update the boost', () => { - mount({ - searchSettings: searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - }), - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateBoostSelectOption( - 'foo', - 1, - 'function', - FunctionalBoostFunction.Exponential - ); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( - searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - function: FunctionalBoostFunction.Exponential, - }) - ); - }); - - it('can also update operation', () => { - mount({ - searchSettings: searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - }), - }); - jest.spyOn(RelevanceTuningLogic.actions, 'setSearchSettings'); - - RelevanceTuningLogic.actions.updateBoostSelectOption( - 'foo', - 1, - 'operation', - BoostOperation.Add - ); - - expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( - searchSettingsWithBoost({ - factor: 1, - type: BoostType.Functional, - operation: BoostOperation.Add, - }) - ); - }); - }); - - describe('setSearchQuery', () => { - it('should update search results', () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'getSearchResults'); - - RelevanceTuningLogic.actions.setSearchQuery('foo'); - - expect(RelevanceTuningLogic.actions.getSearchResults).toHaveBeenCalled(); - }); - }); - - describe('setPrecision', () => { - it('should update search results', () => { - mount(); - jest.spyOn(RelevanceTuningLogic.actions, 'getSearchResults'); - - RelevanceTuningLogic.actions.setPrecision(9); - - expect(RelevanceTuningLogic.actions.getSearchResults).toHaveBeenCalled(); - }); - }); - }); - - describe('selectors', () => { - describe('engineHasSchemaFields', () => { - it('should return false if there is only a single field in a schema', () => { - // This is because if a schema only has a single field, it is "id", which we do not - // consider a tunable field. - mount({ - schema: { - id: { type: 'text', capabilities: {} }, - }, - }); - expect(RelevanceTuningLogic.values.engineHasSchemaFields).toEqual(false); - }); - - it('should return true otherwise', () => { - mount({ - schema: { - id: { type: 'text', capabilities: {} }, - text_field: { type: 'text', capabilities: { fulltext: true } }, - }, - }); - expect(RelevanceTuningLogic.values.engineHasSchemaFields).toEqual(true); - }); - }); - - describe('schemaFields', () => { - it('should return the list of field names from the schema', () => { - mount({ - schema: { - fulltext_only_field: { - type: 'text', - capabilities: { - fulltext: true, - }, - }, - boost_only_field: { - type: 'text', - capabilities: { - boost: true, - }, - }, - fulltext_and_boost_field: { - type: 'text', - capabilities: { - fulltext: true, - boost: true, - }, - }, - ignored_field: { - type: 'text', - capabilities: { - fulltext: false, - boost: false, - }, - }, - }, - }); - expect(RelevanceTuningLogic.values.schemaFields).toEqual([ - 'fulltext_only_field', - 'boost_only_field', - 'fulltext_and_boost_field', - ]); - }); - }); - - describe('schemaFieldsWithConflicts', () => { - it('should return the list of field names that have schema conflicts', () => { - mount({ - schemaConflicts: { - foo: { - text: ['source_engine_1'], - number: ['source_engine_2'], - }, - }, - }); - expect(RelevanceTuningLogic.values.schemaFieldsWithConflicts).toEqual(['foo']); - }); - }); - - describe('filteredSchemaFields', () => { - it('should return a list of schema field names that contain the text from filterInputValue ', () => { - mount({ - filterInputValue: 'fu', - schema: { - fulltext_only_field: { - type: 'text', - capabilities: { - fulltext: true, - }, - }, - boost_only_field: { - type: 'text', - capabilities: { - boost: true, - }, - }, - fulltext_and_boost_field: { - type: 'text', - capabilities: { - fulltext: true, - boost: true, - }, - }, - ignored_field: { - type: 'text', - capabilities: { - fulltext: false, - boost: false, - }, - }, - }, - }); - expect(RelevanceTuningLogic.values.filteredSchemaFields).toEqual([ - 'fulltext_only_field', - 'fulltext_and_boost_field', - ]); - }); - }); - - describe('filteredSchemaFieldsWithConflicts', () => { - it('should return a list of schema field names that contain the text from filterInputValue, and if that field has a schema conflict', () => { - mount({ - filterInputValue: 'ba', - schema: {}, - schemaConflicts: { - bar: { - text: ['source_engine_1'], - number: ['source_engine_2'], - }, - }, - }); - expect(RelevanceTuningLogic.values.filteredSchemaFieldsWithConflicts).toEqual(['bar']); - }); - }); - - describe('isPrecisionTuningEnabled', () => { - it('should be set based on precision enabled', () => { - mount({ - searchSettings: { - precision_enabled: true, - }, - }); - - expect(RelevanceTuningLogic.values.isPrecisionTuningEnabled).toEqual(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts deleted file mode 100644 index 20f4437852ffe..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ /dev/null @@ -1,515 +0,0 @@ -/* - * 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 { omit, cloneDeep, isEmpty } from 'lodash'; - -import type { SearchResult } from '@elastic/search-ui'; - -import { - flashSuccessToast, - flashAPIErrors, - clearFlashMessages, -} from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { AdvancedSchema, SchemaConflicts } from '../../../shared/schema/types'; - -import { EngineLogic } from '../engine'; - -import { - UPDATE_SUCCESS_MESSAGE, - RESET_CONFIRMATION_MESSAGE, - DELETE_SUCCESS_MESSAGE, - DELETE_CONFIRMATION_MESSAGE, - SUCCESS_CHANGES_MESSAGE, - BOOST_TYPE_TO_EMPTY_BOOST, -} from './constants'; -import { Boost, BoostFunction, BoostOperation, BoostType, SearchSettings } from './types'; -import { - filterIfTerm, - parseBoostCenter, - removeBoostStateProps, - normalizeBoostValues, - removeEmptyValueBoosts, -} from './utils'; - -interface RelevanceTuningProps { - searchSettings: SearchSettings; - schema: AdvancedSchema; - schemaConflicts?: SchemaConflicts; -} - -interface RelevanceTuningActions { - onInitializeRelevanceTuning(props: RelevanceTuningProps): RelevanceTuningProps; - setSearchSettings(searchSettings: SearchSettings): { searchSettings: SearchSettings }; - setFilterValue(value: string): string; - setSearchQuery(value: string): string; - setSearchResults(searchResults: SearchResult[]): SearchResult[]; - setResultsLoading(resultsLoading: boolean): boolean; - clearSearchResults(): void; - resetSearchSettingsState(): void; - initializeRelevanceTuning(): void; - getSearchResults(): void; - setSearchSettingsResponse(searchSettings: SearchSettings): { searchSettings: SearchSettings }; - onSearchSettingsSuccess(searchSettings: SearchSettings): { searchSettings: SearchSettings }; - onSearchSettingsError(): void; - updateSearchSettings(): void; - resetSearchSettings(): void; - toggleSearchField(name: string, disableField: boolean): { name: string; disableField: boolean }; - updateFieldWeight(name: string, weight: number): { name: string; weight: number }; - addBoost(name: string, type: BoostType): { name: string; type: BoostType }; - deleteBoost(name: string, index: number): { name: string; index: number }; - updateBoostFactor( - name: string, - index: number, - factor: number - ): { name: string; index: number; factor: number }; - updateBoostValue( - name: string, - boostIndex: number, - updatedValues: string[] - ): { name: string; boostIndex: number; updatedValues: string[] }; - updateBoostCenter( - name: string, - boostIndex: number, - value: string | number - ): { name: string; boostIndex: number; value: string | number }; - updateBoostSelectOption( - name: string, - boostIndex: number, - optionType: keyof Pick, - value: BoostOperation | BoostFunction - ): { - name: string; - boostIndex: number; - optionType: keyof Pick; - value: string; - }; - setPrecision(precision: number): { precision: number }; -} - -interface RelevanceTuningValues { - searchSettings: SearchSettings; - schema: AdvancedSchema; - schemaFields: string[]; - schemaFieldsWithConflicts: string[]; - filteredSchemaFields: string[]; - filteredSchemaFieldsWithConflicts: string[]; - schemaConflicts: SchemaConflicts; - engineHasSchemaFields: boolean; - filterInputValue: string; - query: string; - unsavedChanges: boolean; - dataLoading: boolean; - searchResults: SearchResult[] | null; - resultsLoading: boolean; - isPrecisionTuningEnabled: boolean; -} - -export const RelevanceTuningLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'relevance_tuning_logic'], - actions: () => ({ - onInitializeRelevanceTuning: (props) => props, - setSearchSettings: (searchSettings) => ({ searchSettings }), - setFilterValue: (value) => value, - setSearchQuery: (query) => query, - setSearchResults: (searchResults) => searchResults, - setResultsLoading: (resultsLoading) => resultsLoading, - clearSearchResults: true, - resetSearchSettingsState: true, - initializeRelevanceTuning: true, - getSearchResults: true, - setSearchSettingsResponse: (searchSettings) => ({ - searchSettings, - }), - onSearchSettingsSuccess: (searchSettings) => ({ searchSettings }), - onSearchSettingsError: () => true, - updateSearchSettings: true, - resetSearchSettings: true, - toggleSearchField: (name, disableField) => ({ name, disableField }), - updateFieldWeight: (name, weight) => ({ name, weight }), - addBoost: (name, type) => ({ name, type }), - deleteBoost: (name, index) => ({ name, index }), - updateBoostFactor: (name, index, factor) => ({ name, index, factor }), - updateBoostValue: (name, boostIndex, updatedValues) => ({ name, boostIndex, updatedValues }), - updateBoostCenter: (name, boostIndex, value) => ({ name, boostIndex, value }), - updateBoostSelectOption: (name, boostIndex, optionType, value) => ({ - name, - boostIndex, - optionType, - value, - }), - setPrecision: (precision) => ({ precision }), - }), - // @ts-expect-error upgrade typescript v5.1.6 - reducers: () => ({ - searchSettings: [ - { - search_fields: {}, - boosts: {}, - precision: 2, - precision_enabled: false, - }, - { - // @ts-expect-error upgrade typescript v5.1.6 - onInitializeRelevanceTuning: (_, { searchSettings }) => searchSettings, - // @ts-expect-error upgrade typescript v5.1.6 - setSearchSettings: (_, { searchSettings }) => searchSettings, - // @ts-expect-error upgrade typescript v5.1.6 - setSearchSettingsResponse: (_, { searchSettings }) => searchSettings, - // @ts-expect-error upgrade typescript v5.1.6 - setPrecision: (currentSearchSettings, { precision }) => ({ - ...currentSearchSettings, - precision, - }), - }, - ], - schema: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onInitializeRelevanceTuning: (_, { schema }) => schema, - }, - ], - schemaConflicts: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onInitializeRelevanceTuning: (_, { schemaConflicts }) => schemaConflicts || {}, - }, - ], - filterInputValue: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setFilterValue: (_, filterInputValue) => filterInputValue, - }, - ], - query: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setSearchQuery: (_, query) => query, - }, - ], - unsavedChanges: [ - false, - { - setPrecision: () => true, - setSearchSettings: () => true, - setSearchSettingsResponse: () => false, - }, - ], - - dataLoading: [ - true, - { - onInitializeRelevanceTuning: () => false, - resetSearchSettingsState: () => true, - }, - ], - searchResults: [ - null, - { - clearSearchResults: () => null, - // @ts-expect-error upgrade typescript v5.1.6 - setSearchResults: (_, searchResults) => searchResults, - }, - ], - resultsLoading: [ - false, - { - // @ts-expect-error upgrade typescript v5.1.6 - setResultsLoading: (_, resultsLoading) => resultsLoading, - setSearchResults: () => false, - }, - ], - }), - selectors: ({ selectors }) => ({ - schemaFields: [ - () => [selectors.schema], - (schema: AdvancedSchema) => - Object.entries(schema).reduce( - (fields: string[], [fieldName, { capabilities: fieldCapabilities }]) => { - return fieldCapabilities.fulltext || fieldCapabilities.boost - ? [...fields, fieldName] - : fields; - }, - [] - ), - ], - schemaFieldsWithConflicts: [ - () => [selectors.schemaConflicts], - (schemaConflicts: SchemaConflicts) => Object.keys(schemaConflicts), - ], - filteredSchemaFields: [ - () => [selectors.schemaFields, selectors.filterInputValue], - (schemaFields: string[], filterInputValue: string): string[] => - filterIfTerm(schemaFields, filterInputValue), - ], - filteredSchemaFieldsWithConflicts: [ - () => [selectors.schemaFieldsWithConflicts, selectors.filterInputValue], - (schemaFieldsWithConflicts: string[], filterInputValue: string): string[] => - filterIfTerm(schemaFieldsWithConflicts, filterInputValue), - ], - engineHasSchemaFields: [ - () => [selectors.schemaFields], - (schemaFields: string[]): boolean => schemaFields.length > 0, - ], - isPrecisionTuningEnabled: [ - () => [selectors.searchSettings], - (searchSettings: SearchSettings): boolean => searchSettings.precision_enabled, - ], - }), - listeners: ({ actions, values }) => ({ - initializeRelevanceTuning: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const url = `/internal/app_search/engines/${engineName}/search_settings/details`; - - try { - const response = await http.get(url); - actions.onInitializeRelevanceTuning({ - ...response, - searchSettings: { - ...response.searchSettings, - boosts: normalizeBoostValues(response.searchSettings.boosts), - }, - }); - } catch (e) { - flashAPIErrors(e); - } - }, - getSearchResults: async (_, breakpoint) => { - await breakpoint(250); - - const query = values.query; - if (!query) return actions.clearSearchResults(); - - const { engineName } = EngineLogic.values; - const { http } = HttpLogic.values; - const { - search_fields: searchFields, - boosts, - precision, - } = removeBoostStateProps(values.searchSettings); - const url = `/internal/app_search/engines/${engineName}/search`; - - actions.setResultsLoading(true); - - const filteredBoosts = removeEmptyValueBoosts(boosts); - const precisionSettings = values.isPrecisionTuningEnabled ? { precision } : {}; - - try { - const response = await http.post<{ results: SearchResult[] }>(url, { - query: { - query, - }, - body: JSON.stringify({ - boosts: isEmpty(filteredBoosts) ? undefined : filteredBoosts, - search_fields: isEmpty(searchFields) ? undefined : searchFields, - ...precisionSettings, - }), - }); - - actions.setSearchResults(response.results); - clearFlashMessages(); - } catch (e) { - flashAPIErrors(e); - } - }, - setSearchSettings: () => { - actions.getSearchResults(); - }, - onSearchSettingsSuccess: ({ searchSettings }) => { - actions.setSearchSettingsResponse(searchSettings); - actions.getSearchResults(); - window.scrollTo(0, 0); - }, - onSearchSettingsError: () => { - window.scrollTo(0, 0); - }, - updateSearchSettings: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const url = `/internal/app_search/engines/${engineName}/search_settings`; - - try { - const response = await http.put(url, { - body: JSON.stringify(removeBoostStateProps(values.searchSettings)), - }); - flashSuccessToast(UPDATE_SUCCESS_MESSAGE, { text: SUCCESS_CHANGES_MESSAGE }); - actions.onSearchSettingsSuccess(response); - } catch (e) { - flashAPIErrors(e); - actions.onSearchSettingsError(); - } finally { - const { invalidBoosts, unsearchedUnconfirmedFields } = EngineLogic.values.engine; - if (invalidBoosts || unsearchedUnconfirmedFields) { - // Re-fetch engine data so that any navigation flags are updated dynamically - EngineLogic.actions.initializeEngine(); - } - } - }, - resetSearchSettings: async () => { - if (window.confirm(RESET_CONFIRMATION_MESSAGE)) { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const url = `/internal/app_search/engines/${engineName}/search_settings/reset`; - - try { - const response = await http.post(url); - flashSuccessToast(DELETE_SUCCESS_MESSAGE, { text: SUCCESS_CHANGES_MESSAGE }); - actions.onSearchSettingsSuccess(response); - } catch (e) { - flashAPIErrors(e); - actions.onSearchSettingsError(); - } - } - }, - toggleSearchField: ({ name, disableField }) => { - const { searchSettings } = values; - - const searchFields = disableField - ? omit(searchSettings.search_fields, name) - : { ...searchSettings.search_fields, [name]: { weight: 1 } }; - - actions.setSearchSettings({ - ...searchSettings, - search_fields: searchFields, - }); - }, - updateFieldWeight: ({ name, weight }) => { - const { searchSettings } = values; - const { search_fields: searchFields } = searchSettings; - - actions.setSearchSettings({ - ...searchSettings, - search_fields: { - ...searchFields, - [name]: { - ...searchFields[name], - weight: Math.round(weight * 10) / 10, - }, - }, - }); - }, - addBoost: ({ name, type }) => { - const { searchSettings } = values; - const { boosts } = searchSettings; - const emptyBoost = BOOST_TYPE_TO_EMPTY_BOOST[type]; - let boostArray; - - if (Array.isArray(boosts[name])) { - boostArray = boosts[name].slice(); - boostArray.push(emptyBoost); - } else { - boostArray = [emptyBoost]; - } - - actions.setSearchSettings({ - ...searchSettings, - boosts: { - ...boosts, - [name]: boostArray, - }, - }); - }, - deleteBoost: ({ name, index }) => { - if (window.confirm(DELETE_CONFIRMATION_MESSAGE)) { - const { searchSettings } = values; - const { boosts } = searchSettings; - const boostsRemoved = boosts[name].slice(); - boostsRemoved.splice(index, 1); - const updatedBoosts = { ...boosts }; - - if (boostsRemoved.length > 0) { - updatedBoosts[name] = boostsRemoved; - } else { - delete updatedBoosts[name]; - } - - actions.setSearchSettings({ - ...searchSettings, - boosts: updatedBoosts, - }); - } - }, - updateBoostFactor: ({ name, index, factor }) => { - const { searchSettings } = values; - const { boosts } = searchSettings; - const updatedBoosts = cloneDeep(boosts[name]); - updatedBoosts[index].factor = Math.round(factor * 10) / 10; - - actions.setSearchSettings({ - ...searchSettings, - boosts: { - ...boosts, - [name]: updatedBoosts, - }, - }); - }, - updateBoostValue: ({ name, boostIndex, updatedValues }) => { - const { searchSettings } = values; - const { boosts } = searchSettings; - const updatedBoosts: Boost[] = cloneDeep(boosts[name]); - updatedBoosts[boostIndex].value = updatedValues; - - actions.setSearchSettings({ - ...searchSettings, - boosts: { - ...boosts, - [name]: updatedBoosts, - }, - }); - }, - updateBoostCenter: ({ name, boostIndex, value }) => { - const { searchSettings } = values; - const { boosts } = searchSettings; - const updatedBoosts = cloneDeep(boosts[name]); - const fieldType = values.schema[name].type; - updatedBoosts[boostIndex].center = parseBoostCenter(fieldType, value); - - actions.setSearchSettings({ - ...searchSettings, - boosts: { - ...boosts, - [name]: updatedBoosts, - }, - }); - }, - updateBoostSelectOption: ({ name, boostIndex, optionType, value }) => { - const { searchSettings } = values; - const { boosts } = searchSettings; - const updatedBoosts = cloneDeep(boosts[name]); - if (optionType === 'operation') { - updatedBoosts[boostIndex][optionType] = value as BoostOperation; - } else { - updatedBoosts[boostIndex][optionType] = value as BoostFunction; - } - - actions.setSearchSettings({ - ...searchSettings, - boosts: { - ...boosts, - [name]: updatedBoosts, - }, - }); - }, - setSearchQuery: () => { - actions.getSearchResults(); - }, - setPrecision: () => { - actions.getSearchResults(); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.test.tsx deleted file mode 100644 index 32325c13efdae..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.test.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiFieldSearch } from '@elastic/eui'; - -import { Result } from '../result/result'; - -import { RelevanceTuningPreview } from './relevance_tuning_preview'; - -describe('RelevanceTuningPreview', () => { - const result1 = { id: { raw: 1 } }; - const result2 = { id: { raw: 2 } }; - const result3 = { id: { raw: 3 } }; - - const actions = { - setSearchQuery: jest.fn(), - }; - - const values = { - searchResults: [result1, result2, result3], - engineName: 'foo', - isMetaEngine: false, - schema: {}, - }; - - beforeAll(() => { - setMockActions(actions); - setMockValues(values); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldSearch).prop('placeholder')).toBe('Search foo'); - - const results = wrapper.find(Result); - expect(results.length).toBe(3); - expect(results.at(0).prop('result')).toBe(result1); - expect(results.at(0).prop('isMetaEngine')).toBe(false); - expect(results.at(0).prop('showScore')).toBe(true); - expect(results.at(0).prop('schemaForTypeHighlights')).toBe(values.schema); - - expect(results.at(1).prop('result')).toBe(result2); - expect(results.at(2).prop('result')).toBe(result3); - - expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(false); - expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(false); - }); - - it('correctly indicates whether or not this is a meta engine in results', () => { - setMockValues({ - ...values, - isMetaEngine: true, - }); - - const wrapper = shallow(); - - const results = wrapper.find(Result); - expect(results.at(0).prop('isMetaEngine')).toBe(true); - expect(results.at(1).prop('isMetaEngine')).toBe(true); - expect(results.at(2).prop('isMetaEngine')).toBe(true); - }); - - it('renders a search box that will update search results whenever it is changed', () => { - const wrapper = shallow(); - - wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'some search text' } }); - - expect(actions.setSearchQuery).toHaveBeenCalledWith('some search text'); - }); - - it('will show user a prompt to enter a query if they have not entered one', () => { - setMockValues({ - ...values, - // Since `searchResults` is initialized as undefined, an undefined value indicates - // that no query has been performed, which means they have no yet entered a query - searchResults: undefined, - }); - - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(false); - }); - - it('will show user a no results message if their query returns no results', () => { - setMockValues({ - ...values, - searchResults: [], - }); - - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(false); - expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx deleted file mode 100644 index 3a36a3df2b472..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiFieldSearch, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { EngineLogic } from '../engine'; -import { Result } from '../result/result'; - -import { RelevanceTuningLogic } from '.'; - -const emptyCallout = ( - -); - -const noResultsCallout = ( - -); - -export const RelevanceTuningPreview: React.FC = () => { - const { setSearchQuery } = useActions(RelevanceTuningLogic); - const { searchResults, schema } = useValues(RelevanceTuningLogic); - const { engineName, isMetaEngine } = useValues(EngineLogic); - - return ( - - -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title', { - defaultMessage: 'Preview', - })} -

    -
    - - setSearchQuery(e.target.value)} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.searchPlaceholder', - { - defaultMessage: 'Search {engineName}', - values: { - engineName, - }, - } - )} - fullWidth - /> - {!searchResults && emptyCallout} - {searchResults && searchResults.length === 0 && noResultsCallout} - {searchResults && - searchResults.map((result) => { - return ( - - - - - ); - })} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts deleted file mode 100644 index 63283b7ef6588..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 enum BoostType { - Value = 'value', - Functional = 'functional', - Proximity = 'proximity', -} - -export enum FunctionalBoostFunction { - Logarithmic = 'logarithmic', - Exponential = 'exponential', - Linear = 'linear', -} - -export enum ProximityBoostFunction { - Gaussian = 'gaussian', - Exponential = 'exponential', - Linear = 'linear', -} - -export type BoostFunction = FunctionalBoostFunction | ProximityBoostFunction; - -export enum BoostOperation { - Add = 'add', - Multiply = 'multiply', -} -export interface Boost { - type: BoostType; - operation?: BoostOperation; - function?: BoostFunction; - newBoost?: boolean; - center?: string | number; - factor: number; - value?: string[]; -} - -// A boost that comes from the server, before we normalize it has a much looser schema -export interface RawBoost extends Omit { - value?: string | number | boolean | object | Array; -} - -export interface ValueBoost extends Boost { - value: string[]; - operation: undefined; - function: undefined; -} - -export interface FunctionalBoost extends Boost { - value: undefined; - operation: BoostOperation; - function: FunctionalBoostFunction; -} - -export interface ProximityBoost extends Boost { - value: undefined; - operation: undefined; - function: ProximityBoostFunction; -} -export interface SearchField { - weight: number; -} - -export interface SearchSettingsRequest { - boosts: Record; - search_fields: Record; - result_fields?: object; - precision: number; -} - -export interface SearchSettings extends SearchSettingsRequest { - precision_enabled: boolean; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts deleted file mode 100644 index 1a70b3f6e1d8e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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 { SchemaType } from '../../../shared/schema/types'; - -import { Boost, BoostType } from './types'; -import { - filterIfTerm, - normalizeBoostValues, - removeBoostStateProps, - parseBoostCenter, - removeEmptyValueBoosts, -} from './utils'; - -describe('filterIfTerm', () => { - it('will filter a list of strings to a list of strings containing the specified string', () => { - expect(filterIfTerm(['jalepeno', 'no', 'not', 'panorama', 'truck'], 'no')).toEqual([ - 'jalepeno', - 'no', - 'not', - 'panorama', - ]); - }); - - it('will not filter at all if an empty string is provided', () => { - expect(filterIfTerm(['jalepeno', 'no', 'not', 'panorama', 'truck'], '')).toEqual([ - 'jalepeno', - 'no', - 'not', - 'panorama', - 'truck', - ]); - }); -}); - -describe('removeBoostStateProps', () => { - it('will remove the newBoost flag from boosts within the provided searchSettings object', () => { - const searchSettings = { - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - newBoost: true, - value: [''], - }, - ], - }, - search_fields: { - foo: { - weight: 1, - }, - }, - precision: 10, - precision_enabled: true, - }; - const { precision_enabled: precisionEnabled, ...searchSettingsWithoutPrecisionEnabled } = - searchSettings; - expect(removeBoostStateProps(searchSettings)).toEqual({ - ...searchSettingsWithoutPrecisionEnabled, - boosts: { - foo: [ - { - type: BoostType.Value, - factor: 5, - value: [''], - }, - ], - }, - }); - }); -}); - -describe('parseBoostCenter', () => { - it('should parse the value to a number when the type is number', () => { - expect(parseBoostCenter(SchemaType.Number, 5)).toEqual(5); - expect(parseBoostCenter(SchemaType.Number, '5')).toEqual(5); - }); - - it('should not try to parse the value when the type is text', () => { - expect(parseBoostCenter(SchemaType.Text, 5)).toEqual(5); - expect(parseBoostCenter(SchemaType.Text, '4')).toEqual('4'); - }); - - it('should leave text invalid numbers alone', () => { - expect(parseBoostCenter(SchemaType.Number, 'foo')).toEqual('foo'); - }); -}); - -describe('normalizeBoostValues', () => { - const boosts = { - foo: [ - { - type: BoostType.Value, - factor: 9.5, - value: 1, - }, - { - type: BoostType.Value, - factor: 9.5, - value: '1', - }, - { - type: BoostType.Value, - factor: 9.5, - value: [1], - }, - { - type: BoostType.Value, - factor: 9.5, - value: ['1'], - }, - { - type: BoostType.Value, - factor: 9.5, - value: [ - '1', - 1, - '2', - 2, - true, - { - b: 'a', - }, - {}, - ], - }, - ], - bar: [ - { - type: BoostType.Proximity, - factor: 9.5, - }, - ], - sp_def: [ - { - type: BoostType.Functional, - factor: 5, - }, - ], - }; - - it('converts all value types to string for consistency', () => { - expect(normalizeBoostValues(boosts)).toEqual({ - bar: [{ factor: 9.5, type: BoostType.Proximity }], - foo: [ - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { - factor: 9.5, - type: BoostType.Value, - value: ['1', '1', '2', '2', 'true', '[object Object]', '[object Object]'], - }, - ], - sp_def: [{ type: BoostType.Functional, factor: 5 }], - }); - }); -}); - -describe('removeEmptyValueBoosts', () => { - const boosts: Record = { - bar: [ - { factor: 9.5, type: BoostType.Proximity }, - { type: BoostType.Functional, factor: 5 }, - ], - foo: [ - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { factor: 9.5, type: BoostType.Value, value: ['1', '', ' '] }, - { factor: 9.5, type: BoostType.Value, value: [] }, - { factor: 9.5, type: BoostType.Value, value: ['', '1'] }, - ], - baz: [{ factor: 9.5, type: BoostType.Value, value: [''] }], - }; - - expect(removeEmptyValueBoosts(boosts)).toEqual({ - bar: [ - { factor: 9.5, type: BoostType.Proximity }, - { type: BoostType.Functional, factor: 5 }, - ], - foo: [ - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - { factor: 9.5, type: BoostType.Value, value: ['1'] }, - ], - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts deleted file mode 100644 index 843ec471f21a4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 { cloneDeep, omit } from 'lodash'; - -import { SchemaType } from '../../../shared/schema/types'; - -import { - RawBoost, - Boost, - SearchSettingsRequest, - SearchSettings, - BoostType, - ValueBoost, -} from './types'; - -// If the user hasn't entered a filter, then we can skip filtering the array entirely -export const filterIfTerm = (array: string[], filterTerm: string): string[] => { - return filterTerm === '' ? array : array.filter((item) => item.includes(filterTerm)); -}; - -export const removeBoostStateProps = (searchSettings: SearchSettings): SearchSettingsRequest => { - const { precision_enabled: precisionEnabled, ...updatedSettings } = cloneDeep(searchSettings); - const { boosts } = updatedSettings; - const keys = Object.keys(boosts); - keys.forEach((key) => boosts[key].forEach((boost) => delete boost.newBoost)); - - return updatedSettings; -}; - -export const parseBoostCenter = (fieldType: SchemaType, value: string | number) => { - // Leave non-numeric fields alone - if (fieldType === SchemaType.Number) { - const floatValue = parseFloat(value as string); - return isNaN(floatValue) ? value : floatValue; - } - return value; -}; - -const toArray = (v: T | T[]): T[] => (Array.isArray(v) ? v : [v]); -const toString = (v1: T) => String(v1); - -const normalizeBoostValue = (boost: RawBoost): Boost => { - if (!Object.hasOwn(boost, 'value')) { - // Can't simply do `return boost` here as TS can't infer the correct type - return omit(boost, 'value'); - } - - return { - ...boost, - type: boost.type as BoostType, - value: toArray(boost.value).map(toString), - }; -}; - -// Data may have been set to invalid types directly via the public App Search API. Since these are invalid, we don't want to deal -// with them as valid types in the UI. For that reason, we stringify all values here, as the data comes in. -// Additionally, values can be in single values or in arrays. -export const normalizeBoostValues = (boosts: Record): Record => - Object.entries(boosts).reduce((newBoosts, [fieldName, boostList]) => { - return { - ...newBoosts, - [fieldName]: boostList.map(normalizeBoostValue), - }; - }, {}); - -// Our model allows for empty values to be added to boosts. However, the server will not accept -// empty strings in values. To avoid that, we filter out empty values before sending them to the server. - -// I.e., the server will not accept any of the following, so we need to filter them out -// value: undefined -// value: [] -// value: [''] -// value: ['foo', ''] -export const removeEmptyValueBoosts = ( - boosts: Record -): Record => { - // before: - // { foo: { values: ['a', '', ' '] } } - // { foo: { values: [''] } } - // after: - // { foo: { values: ['a'] } } - const filterEmptyValueBoosts = (fieldBoosts: Boost[]) => { - return fieldBoosts.filter((boost: Boost) => { - if (boost.type !== BoostType.Value) return true; - - const valueBoost = boost as ValueBoost; - const filteredValues = valueBoost.value.filter((value) => value.trim() !== ''); - - if (filteredValues.length) { - boost.value = filteredValues; - return true; - } else { - return false; - } - }); - }; - - return Object.entries(boosts).reduce((acc, [fieldName, fieldBoosts]) => { - const updatedBoosts = filterEmptyValueBoosts(fieldBoosts); - return updatedBoosts.length ? { ...acc, [fieldName]: updatedBoosts } : acc; - }, {}); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts deleted file mode 100644 index a7eed3b95e6fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { ResultFieldValue } from './result_field_value'; -export { ResultToken } from './result_token'; -export { Result } from './result'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss deleted file mode 100644 index 93bace1d77775..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss +++ /dev/null @@ -1,46 +0,0 @@ -.appSearchResult { - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: auto 1fr; - grid-template-areas: - 'drag content' - 'drag toggle'; - overflow: hidden; // Prevents child background-colors from clipping outside of panel border-radius - border: $euiBorderThin; // TODO: Remove after EUI version is bumped beyond 31.8.0 - - &__content { - grid-area: content; - width: 100%; - padding: $euiSize; - overflow: hidden; - color: $euiTextColor; - } - - &__hiddenFieldsToggle { - grid-area: toggle; - display: flex; - justify-content: center; - padding: $euiSizeS; - border-top: $euiBorderThin; - font-size: $euiFontSizeXS; - color: $euiColorPrimary; - - &:hover, - &:focus { - background-color: $euiPageBackgroundColor; - } - - .euiIcon { - margin-left: $euiSizeXS; - } - } - - &__dragHandle { - grid-area: drag; - display: flex; - justify-content: center; - align-items: center; - width: $euiSizeXL; - border-right: $euiBorderThin; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx deleted file mode 100644 index 0720ce5b31c22..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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 { mockKibanaValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiBadge, EuiPanel } from '@elastic/eui'; - -import { SchemaType } from '../../../shared/schema/types'; -import { mountWithIntl } from '../../../test_helpers'; - -import { Result } from './result'; -import { ResultField } from './result_field'; -import { ResultHeader } from './result_header'; - -describe('Result', () => { - const props = { - isMetaEngine: false, - result: { - id: { - raw: '1', - }, - title: { - raw: 'A title', - }, - description: { - raw: 'A description', - }, - length: { - raw: 100, - }, - _meta: { - id: '1', - score: 100, - engine: 'my-engine', - }, - }, - }; - - const schema = { - title: SchemaType.Text, - description: SchemaType.Text, - length: SchemaType.Number, - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiPanel).exists()).toBe(true); - expect(wrapper.find(EuiPanel).prop('title')).toEqual('Document 1'); - }); - - it('should render a ResultField for each field except id and _meta', () => { - const wrapper = shallow(); - expect(wrapper.find(ResultField).map((rf) => rf.prop('field'))).toEqual([ - 'title', - 'description', - 'length', - ]); - }); - - describe('header', () => { - it('renders a header', () => { - const wrapper = shallow(); - const header = wrapper.find(ResultHeader); - - expect(header.exists()).toBe(true); - expect(header.prop('isMetaEngine')).toBe(true); // passed through from props - expect(header.prop('showScore')).toBe(true); // passed through from props - expect(header.prop('resultMeta')).toEqual({ - id: '1', - score: 100, - engine: 'my-engine', - }); // passed through from meta in result prop - expect(header.prop('documentLink')).toBe(undefined); // based on shouldLinkToDetailPage prop - }); - - it('passes documentLink when shouldLinkToDetailPage is true', () => { - const wrapper = shallow(); - const header = wrapper.find(ResultHeader); - - expect(header.prop('documentLink')).toBe('/engines/my-engine/documents/1'); - }); - - it('contains the result position if one is passed', () => { - const wrapper = mountWithIntl(); - const header = wrapper.find(ResultHeader); - expect(header.find(EuiBadge).text()).toContain('#4'); - }); - }); - - describe('actions', () => { - const actions = [ - { - title: 'Hide', - onClick: jest.fn(), - iconType: 'eyeClosed', - }, - { - title: 'Bookmark', - onClick: jest.fn(), - iconType: 'starFilled', - }, - ]; - - it('passes actions to the header', () => { - const wrapper = shallow(); - expect(wrapper.find(ResultHeader).prop('actions')).toEqual(actions); - }); - - it('adds a link action to the start of the actions array if shouldLinkToDetailPage is passed', () => { - const wrapper = shallow(); - - const passedActions = wrapper.find(ResultHeader).prop('actions'); - expect(passedActions.length).toEqual(3); // In addition to the 2 actions passed, we also have a link action - - const linkAction = passedActions[0]; - expect(linkAction.title).toEqual('Visit document details'); - - linkAction.onClick(); - expect(mockKibanaValues.navigateToUrl).toHaveBeenCalledWith('/engines/my-engine/documents/1'); - }); - }); - - describe('dragging', () => { - // In the real world, the drag library sets data attributes, role, tabIndex, etc. - const mockDragHandleProps = { - someMockProp: true, - } as unknown as DraggableProvidedDragHandleProps; - - it('will render a drag handle with the passed props', () => { - const wrapper = shallow(); - - expect(wrapper.find('.appSearchResult__dragHandle')).toHaveLength(1); - expect(wrapper.find('.appSearchResult__dragHandle').prop('someMockProp')).toEqual(true); - }); - }); - - it('will render field details with type highlights if schemaForTypeHighlights has been provided', () => { - const wrapper = shallow(); - expect(wrapper.find(ResultField).map((rf) => rf.prop('type'))).toEqual([ - 'text', - 'text', - 'number', - ]); - }); - - describe('when there are more than 5 fields', () => { - const propsWithMoreFields = { - isMetaEngine: false, - result: { - id: { - raw: '1', - }, - title: { - raw: 'A title', - }, - description: { - raw: 'A description', - }, - length: { - raw: 100, - }, - states: { - raw: ['Pennsylvania', 'Ohio'], - }, - visitors: { - raw: 1000, - }, - size: { - raw: 200, - }, - _meta: { - id: '1', - score: 100, - engine: 'my-engine', - }, - }, - }; - - describe('the initial render', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - wrapper = shallow(); - }); - - it('renders a hidden fields toggle button', () => { - expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true); - }); - - it('renders a collapse icon', () => { - expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false); - }); - - it('does not render an expand icon', () => { - expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true); - }); - - it('shows no more than 5 fields', () => { - expect(wrapper.find(ResultField).length).toEqual(5); - }); - }); - - describe('after clicking the expand button', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - wrapper = shallow(); - expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true); - wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click'); - }); - - it('renders correct toggle text', () => { - expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').text()).toEqual( - 'Hide additional fields' - ); - }); - - it('renders a collapse icon', () => { - expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(true); - }); - - it('does not render an expand icon', () => { - expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(false); - }); - - it('shows all fields', () => { - expect(wrapper.find(ResultField).length).toEqual(6); - }); - }); - - describe('after clicking the collapse button', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - wrapper = shallow(); - expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').exists()).toBe(true); - wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click'); - wrapper.find('.appSearchResult__hiddenFieldsToggle').simulate('click'); - }); - - it('renders correct toggle text', () => { - expect(wrapper.find('.appSearchResult__hiddenFieldsToggle').text()).toEqual( - 'Show 1 additional field' - ); - }); - - it('renders a collapse icon', () => { - expect(wrapper.find('[data-test-subj="CollapseResult"]').exists()).toBe(false); - }); - - it('does not render an expand icon', () => { - expect(wrapper.find('[data-test-subj="ExpandResult"]').exists()).toBe(true); - }); - - it('shows no more than 5 fields', () => { - expect(wrapper.find(ResultField).length).toEqual(5); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx deleted file mode 100644 index 0bee13c58c1b9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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, { useState, useMemo } from 'react'; - -import type { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; - -import './result.scss'; - -import { EuiPanel, EuiIcon } from '@elastic/eui'; - -import type { SearchResult } from '@elastic/search-ui'; -import { i18n } from '@kbn/i18n'; - -import { KibanaLogic } from '../../../shared/kibana'; -import { AdvancedSchema, Schema } from '../../../shared/schema/types'; - -import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../routes'; -import { generateEncodedPath } from '../../utils/encode_path_params'; -import { formatResultWithoutMeta } from '../../utils/results'; - -import { ResultField } from './result_field'; -import { ResultHeader } from './result_header'; -import { FieldValue, ResultAction } from './types'; - -interface Props { - result: SearchResult; - isMetaEngine: boolean; - showScore?: boolean; - resultPosition?: number; - shouldLinkToDetailPage?: boolean; - schemaForTypeHighlights?: Schema | AdvancedSchema; - actions?: ResultAction[]; - dragHandleProps?: DraggableProvidedDragHandleProps | null; - showClick?: boolean; -} - -const RESULT_CUTOFF = 5; - -export const Result: React.FC = ({ - result, - isMetaEngine, - showScore = false, - shouldLinkToDetailPage = false, - schemaForTypeHighlights, - actions = [], - dragHandleProps, - resultPosition, - showClick = false, -}) => { - const [isOpen, setIsOpen] = useState(false); - - const ID = 'id'; - const META = '_meta'; - const resultMeta = result[META]; - const resultFields = useMemo( - () => Object.entries(formatResultWithoutMeta(result)).filter(([key]) => key !== ID), - [result] - ); - const numResults = resultFields.length; - const isAdvancedSchema = (schema: Schema | AdvancedSchema): schema is AdvancedSchema => { - return ( - schema && - Object.values(schema).reduce((isAdvanced, schemaField) => { - return isAdvanced && typeof schemaField !== 'string'; - }, true) - ); - }; - const typeForField = (fieldName: string) => { - if (schemaForTypeHighlights) { - return isAdvancedSchema(schemaForTypeHighlights) - ? schemaForTypeHighlights[fieldName].type - : schemaForTypeHighlights[fieldName]; - } - }; - - const documentLink = shouldLinkToDetailPage - ? generateEncodedPath(ENGINE_DOCUMENT_DETAIL_PATH, { - engineName: resultMeta.engine, - documentId: resultMeta.id, - }) - : undefined; - if (shouldLinkToDetailPage && documentLink) { - const linkAction = { - onClick: () => KibanaLogic.values.navigateToUrl(documentLink), - title: i18n.translate('xpack.enterpriseSearch.appSearch.result.documentDetailLink', { - defaultMessage: 'Visit document details', - }), - iconType: 'eye', - }; - actions = [linkAction, ...actions]; - } - - return ( - - {dragHandleProps && ( -
    - -
    - )} -
    - - {resultFields - .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) - .map(([field, value]: [string, FieldValue]) => ( - - ))} -
    - {numResults > RESULT_CUTOFF && ( - - )} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx deleted file mode 100644 index eaa625c5c15ee..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; - -import { ResultActions } from './result_actions'; - -describe('ResultActions', () => { - const actions = [ - { - title: 'Hide', - onClick: jest.fn(), - iconType: 'eyeClosed', - iconColor: 'danger' as EuiButtonIconProps['color'], - }, - { - title: 'Bookmark', - onClick: jest.fn(), - iconType: 'starFilled', - iconColor: undefined, - }, - ]; - - const wrapper = shallow(); - const buttons = wrapper.find(EuiButtonIcon); - - it('renders an action button for each action passed', () => { - expect(buttons).toHaveLength(2); - }); - - it('passes icon props correctly', () => { - expect(buttons.first().prop('iconType')).toEqual('eyeClosed'); - expect(buttons.first().prop('color')).toEqual('danger'); - - expect(buttons.last().prop('iconType')).toEqual('starFilled'); - // Note that no iconColor was passed so it was defaulted to primary - expect(buttons.last().prop('color')).toEqual('primary'); - }); - - it('passes click events', () => { - buttons.first().simulate('click'); - expect(actions[0].onClick).toHaveBeenCalled(); - - buttons.last().simulate('click'); - expect(actions[1].onClick).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx deleted file mode 100644 index 5eac38b88937c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { ResultAction } from './types'; - -interface Props { - actions: ResultAction[]; -} - -export const ResultActions: React.FC = ({ actions }) => { - return ( - - {actions.map(({ onClick, title, iconType, iconColor, disabled }) => ( - - - - ))} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.scss deleted file mode 100644 index 443c130548f6b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.scss +++ /dev/null @@ -1,35 +0,0 @@ -.appSearchResultField { - font-size: $euiFontSizeS; - line-height: $euiLineHeight; - display: grid; - grid-template-columns: .85fr $euiSizeXL 1fr; - grid-gap: $euiSizeXS; - - &__separator { - text-align: center; - - &:after { - content: '=>'; - color: $euiColorDarkShade; - } - } - - &__key { - display: flex; - align-items: center; - @include euiCodeFont; - - .euiToken { - margin-right: $euiSizeS; - } - } - - &__value { - padding-left: $euiSize; - overflow: hidden; - - @include euiBreakpoint('xs') { - padding-left: 0; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.test.tsx deleted file mode 100644 index d8586c3fb3518..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { InternalSchemaType } from '../../../shared/schema/types'; - -import { ResultField } from './result_field'; - -describe('ResultField', () => { - it('renders', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('ResultFieldValue').exists()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.tsx deleted file mode 100644 index 9b78d769d2f30..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { FieldType, Raw, Snippet } from './types'; - -import { ResultFieldValue, ResultToken } from '.'; - -import './result_field.scss'; - -interface Props { - field: string; - raw?: Raw; - snippet?: Snippet; - type?: FieldType; -} - -export const ResultField: React.FC = ({ field, raw, snippet, type }) => { - return ( -
    -
    - {type && } - {field} -
    -
    -
    - -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.scss deleted file mode 100644 index ae3ed37c67a41..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.scss +++ /dev/null @@ -1,17 +0,0 @@ -.enterpriseSearchDataType { - &--number, - &--float { - color: $euiColorAccentText; - font-family: $euiCodeFontFamily; - } - - &--geolocation, - &--location { - color: $euiColorSuccessText; - font-family: $euiCodeFontFamily; - } - - &--date { - font-family: $euiCodeFontFamily; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.test.tsx deleted file mode 100644 index ebb2c10748d87..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.test.tsx +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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, ShallowWrapper } from 'enzyme'; - -import { SchemaType, InternalSchemaType } from '../../../shared/schema/types'; - -import { ResultFieldValue } from '.'; - -describe('ResultFieldValue', () => { - describe('when no raw or snippet values are provided', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow(); - }); - - it('will render a dash', () => { - expect(wrapper.text()).toEqual('—'); - }); - }); - - describe('when there is only a raw value', () => { - describe('and the value is a string', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow(); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('foo'); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--text'); - }); - }); - - describe('and the value is a string array', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow( - - ); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('["foo", "bar"]'); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--string'); - }); - }); - - describe('and the value is a number', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow(); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('1'); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--number'); - }); - }); - - describe('and the value is an array of numbers', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow(); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('[1, 2]'); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--float'); - }); - }); - - describe('and the value is a location', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow(); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('44.6, -110.5'); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--geolocation'); - }); - }); - - describe('and the value is an array of locations', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow( - - ); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('[44.6, -110.5, 44.7, -111.0]'); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--location'); - }); - }); - - describe('and the value is a date', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow(); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('1872-03-01T06:00:00Z'); - }); - - it('will have the appropriate type class on outer div', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--date'); - }); - }); - - describe('and the value is an array of dates', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow( - - ); - }); - - it('will render a display value', () => { - expect(wrapper.text()).toEqual('[1872-03-01T06:00:00Z, 1472-04-01T06:00:00Z]'); - }); - - it('will have the appropriate type class on outer div', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--date'); - }); - }); - }); - - describe('when there is a snippet value', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - wrapper = shallow( - - ); - }); - - it('will render content as html with mark tags', () => { - expect(wrapper.find('div').html()).toContain( - 'a long description' - ); - }); - - it('will have the appropriate type class', () => { - expect(wrapper.prop('className')).toContain('enterpriseSearchDataType--string'); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.tsx deleted file mode 100644 index 30730dbadc578..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_field_value.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 classNames from 'classnames'; - -import { SchemaType } from '../../../shared/schema/types'; - -import { FieldType, Raw, SimpleFieldValue, Snippet } from './types'; - -import './result_field_value.scss'; - -const isNotNumeric = (raw: string | number): boolean => { - if (typeof raw === 'number') return false; - return isNaN(parseFloat(raw)); -}; - -const isScalarValue = (_: Raw, type?: FieldType): _ is SimpleFieldValue => { - return type !== SchemaType.Nested; -}; - -const getRawDisplay = (raw?: Raw, type?: FieldType): string | null => { - if (!raw) { - return null; - } - - if (!isScalarValue(raw, type)) { - return JSON.stringify(raw); - } - - if (Array.isArray(raw)) { - return getRawArrayDisplay(raw); - } - - return raw.toString(); -}; - -const getRawArrayDisplay = (rawArray: string[] | number[]): string => { - return `[${rawArray.map((raw) => (isNotNumeric(raw) ? `"${raw}"` : raw)).join(', ')}]`; -}; - -const parseHighlights = (highlight: string): string => { - return highlight.replace(/(.+?)<\/em>/gi, '$1'); -}; - -const isFieldValueEmpty = (type?: string, raw?: Raw, snippet?: Snippet) => { - const isNumber = type === 'number'; - if (isNumber) { - return raw === null; - } - - return !snippet && !raw; -}; - -interface Props { - raw?: Raw; - snippet?: Snippet; - type?: FieldType; - className?: string; -} - -export const ResultFieldValue: React.FC = ({ snippet, raw, type, className }) => { - const isEmpty = isFieldValueEmpty(type, raw, snippet); - if (isEmpty) return <>—; - const classes = classNames({ [`enterpriseSearchDataType--${type}`]: !!type }, className); - return ( -
    - {!!snippet ? null : getRawDisplay(raw, type)} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss deleted file mode 100644 index ebae11ee8ad33..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss +++ /dev/null @@ -1,3 +0,0 @@ -.appSearchResultHeader { - margin-bottom: $euiSizeM; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx deleted file mode 100644 index af1a537a79e16..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { EuiBadge } from '@elastic/eui'; - -import { mountWithIntl } from '../../../test_helpers'; - -import { ResultActions } from './result_actions'; -import { ResultHeader } from './result_header'; - -describe('ResultHeader', () => { - const resultMeta = { - id: '1', - score: 100, - engine: 'my-engine', - }; - const props = { - showScore: false, - showClick: false, - isMetaEngine: false, - resultMeta, - actions: [], - }; - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('always renders an id', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultId"]').prop('value')).toEqual('1'); - expect(wrapper.find('[data-test-subj="ResultId"]').prop('href')).toBeUndefined(); - }); - - it('renders id as a link if a documentLink has been passed', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('[data-test-subj="ResultId"]').prop('value')).toEqual('1'); - expect(wrapper.find('[data-test-subj="ResultId"]').prop('href')).toEqual( - '/engines/my-engine/documents/1' - ); - }); - - it('renders position if one is passed in', () => { - const wrapper = mountWithIntl(); - - const badge = wrapper.find(EuiBadge); - expect(badge.text()).toContain('#5'); - }); - - describe('score', () => { - it('renders score if showScore is true ', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultScore"]').prop('value')).toEqual(100); - }); - - it('does not render score if showScore is false', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultScore"]').exists()).toBe(false); - }); - }); - - describe('clicks', () => { - it('renders clicks if showClick is true', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultClicks"]').exists()).toBe(true); - }); - - it(' does not render clicks if showClick is false', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultClicks"]').exists()).toBe(false); - }); - }); - - describe('engine', () => { - it('renders engine name if this is a meta engine', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultEngine"]').prop('value')).toBe('my-engine'); - }); - - it('does not render an engine if this is not a meta engine', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="ResultEngine"]').exists()).toBe(false); - }); - }); - - describe('actions', () => { - const actions = [{ title: 'View document', onClick: () => {}, iconType: 'eye' }]; - - it('renders ResultActions if actions have been passed', () => { - const wrapper = shallow(); - expect(wrapper.find(ResultActions).exists()).toBe(true); - }); - - it('does not render ResultActions if no actions are passed', () => { - const wrapper = shallow(); - expect(wrapper.find(ResultActions).exists()).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx deleted file mode 100644 index dabaa5db29060..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n-react'; - -import { ResultActions } from './result_actions'; -import { ResultHeaderItem } from './result_header_item'; -import { ResultMeta, ResultAction } from './types'; - -import './result_header.scss'; - -interface Props { - showScore: boolean; - isMetaEngine: boolean; - resultMeta: ResultMeta; - actions: ResultAction[]; - documentLink?: string; - resultPosition?: number; - showClick: boolean; -} - -export const ResultHeader: React.FC = ({ - showScore, - resultMeta, - isMetaEngine, - actions, - documentLink, - resultPosition, - showClick, -}) => { - return ( -
    - - {typeof resultPosition !== 'undefined' && ( - - - - - - )} - - - - {showClick && ( - - - - )} - {showScore && ( - - - - )} - {isMetaEngine && ( - - - - )} - {actions.length > 0 && ( - - - - )} - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss deleted file mode 100644 index 94367ae634b7c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss +++ /dev/null @@ -1,12 +0,0 @@ -.appSearchResultHeaderItem { - @include euiCodeFont; - - &__score { - color: $euiColorSuccessText; - } - - .euiFlexItem:not(:first-child):not(:last-child) & { - padding-right: $euiSizeS; - box-shadow: inset (-$euiBorderWidthThin) 0 0 0 $euiBorderColor; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx deleted file mode 100644 index d45eb8856d118..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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, mount } from 'enzyme'; - -import { ResultHeaderItem } from './result_header_item'; - -describe('ResultHeaderItem', () => { - it('renders', () => { - const wrapper = mount(); - expect(wrapper.find('.appSearchResultHeaderItem').text()).toContain('001'); - }); - - it('will truncate long field names', () => { - const wrapper = mount( - - ); - expect(wrapper.find('.appSearchResultHeaderItem').text()).toContain( - 'a-really-really-really-really-…' - ); - }); - - it('will truncate long values', () => { - const wrapper = mount( - - ); - expect(wrapper.find('.appSearchResultHeaderItem').text()).toContain( - 'a-really-really-really-really-…' - ); - }); - - it('will truncate long values from the beginning if the type is "id"', () => { - const wrapper = mount( - - ); - expect(wrapper.find('.appSearchResultHeaderItem').text()).toContain( - '…lly-really-really-really-value' - ); - }); - - it('will round any numeric values that are passed in to 2 decimals, regardless of the explicit "type" passed', () => { - const wrapper = mount(); - expect(wrapper.find('.appSearchResultHeaderItem').text()).toContain('5.19'); - }); - - it('if the value passed in is undefined, it will render "-"', () => { - const wrapper = mount(); - expect(wrapper.find('.appSearchResultHeaderItem').text()).toContain('-'); - }); - - it('it will add a "score" class if the "type" passed is "score"', () => { - const wrapper = shallow(); - expect( - wrapper.find('.appSearchResultHeaderItem').hasClass('appSearchResultHeaderItem__score') - ).toBe(true); - }); - - it('it will render as a link if an href is passed', () => { - const wrapper = shallow( - - ); - expect(wrapper.find('EuiLinkTo').exists()).toBe(true); - expect(wrapper.find('EuiLinkTo').prop('to')).toBe('http://www.example.com'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx deleted file mode 100644 index 32a3794d80a51..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 './result_header_item.scss'; - -import { EuiLinkTo } from '../../../shared/react_router_helpers/eui_components'; - -import { TruncatedContent } from '../../../shared/truncate'; - -interface Props { - field: string; - value?: string | number; - type: 'id' | 'score' | 'string' | 'clicks'; - href?: string; -} - -const MAX_CHARACTER_LENGTH = 30; - -export const ResultHeaderItem: React.FC = ({ field, type, value, href }) => { - let formattedValue = '-'; - if (typeof value === 'string') { - formattedValue = value; - } else if (typeof value === 'number') { - if (type === 'clicks') { - formattedValue = i18n.translate('xpack.enterpriseSearch.appSearch.result.clicks', { - defaultMessage: '{clicks} Clicks', - values: { clicks: value }, - description: 'This is click count for Adaptive Relevance suggestion results', - }); - } else { - formattedValue = parseFloat((value as number).toFixed(2)).toString(); - } - } - - const HeaderItemContent = () => ( - - ); - - return ( - - {type !== 'clicks' && ( - <> - -   - - )} - {href ? ( - - - - ) : ( - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.test.tsx deleted file mode 100644 index a35b373371a66..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { EuiToken } from '@elastic/eui'; - -import { SchemaType } from '../../../shared/schema/types'; - -import { ResultToken } from './result_token'; - -describe('ResultToken', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('render a token icon based on the provided field type', () => { - expect( - shallow() - .find(EuiToken) - .prop('iconType') - ).toBe('tokenString'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx deleted file mode 100644 index 83c31d2ce06f2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_token.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { EuiToken } from '@elastic/eui'; - -import { SchemaType, InternalSchemaType } from '../../../shared/schema/types'; - -import { FieldType } from './types'; - -interface Props { - fieldType: FieldType; -} - -const fieldTypeToTokenMap = { - [SchemaType.Text]: 'tokenString', - [InternalSchemaType.String]: 'tokenString', - [SchemaType.Number]: 'tokenNumber', - [InternalSchemaType.Float]: 'tokenNumber', - [SchemaType.Geolocation]: 'tokenGeo', - [InternalSchemaType.Location]: 'tokenGeo', - [SchemaType.Date]: 'tokenDate', - // @ts-expect-error upgrade typescript v5.1.6 - [InternalSchemaType.Date]: 'tokenDate', - [InternalSchemaType.Nested]: 'tokenNested', -}; - -export const ResultToken: React.FC = ({ fieldType }) => { - return ; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts deleted file mode 100644 index 1d495777c1781..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { EuiButtonIconProps } from '@elastic/eui'; - -import { InternalSchemaType, SchemaType } from '../../../shared/schema/types'; - -export type FieldType = InternalSchemaType | SchemaType; - -export type NestedFieldValue = - | { [fieldName: string]: SimpleFieldValue | NestedFieldValue } - | NestedFieldValue[]; -export type SimpleFieldValue = string | string[] | number | number[]; -export type Raw = SimpleFieldValue | NestedFieldValue; -export type Snippet = string; -export interface FieldValue { - raw?: Raw; - snippet?: Snippet; -} - -export interface ResultMeta { - id: string; - score?: number; - engine: string; - clicks?: number; -} - -export interface ResultAction { - onClick(): void; - title: string; - iconType: string; - iconColor?: EuiButtonIconProps['color']; - disabled?: boolean; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx deleted file mode 100644 index 99f43e70a6fdc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Add documents to adjust settings'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchResultSettings) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx deleted file mode 100644 index 6434a877ead5e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyState: React.FC = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.title', { - defaultMessage: 'Add documents to adjust settings', - })} - - } - body={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.description', - { - defaultMessage: - 'A schema will be automatically created for you after you index some documents.', - } - )} - actions={ - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.buttonLabel', - { defaultMessage: 'Read the result settings guide' } - )} - - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/index.ts deleted file mode 100644 index 8c9e58e6ba0f4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts deleted file mode 100644 index 2d0515146b089..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { FieldResultSetting } from './types'; - -export const DEFAULT_SNIPPET_SIZE = 100; -export const SIZE_FIELD_MINIMUM = 20; -export const SIZE_FIELD_MAXIMUM = 1000; - -export const RESULT_SETTINGS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.title', - { defaultMessage: 'Result Settings' } -); - -export const DEFAULT_FIELD_SETTINGS: FieldResultSetting = { - raw: true, - snippet: false, - snippetFallback: false, -}; - -export const DISABLED_FIELD_SETTINGS: FieldResultSetting = { - raw: false, - snippet: false, - snippetFallback: false, -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts deleted file mode 100644 index 3336de732a508..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { RESULT_SETTINGS_TITLE } from './constants'; -export { ResultSettingsLogic } from './result_settings_logic'; -export { ResultSettings } from './result_settings'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts deleted file mode 100644 index 0bd18ea640850..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { QueryPerformance } from './query_performance'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx deleted file mode 100644 index 604d94f915439..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiBadge } from '@elastic/eui'; - -import { QueryPerformance } from './query_performance'; - -describe('QueryPerformance', () => { - const values = { - queryPerformanceScore: 1, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders as green with the text "optimal" for a performance score of less than 6', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiBadge).prop('color')).toEqual('#59deb4'); - expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: optimal'); - }); - - it('renders as blue with the text "good" for a performance score of less than 11', () => { - setMockValues({ - queryPerformanceScore: 10, - }); - const wrapper = shallow(); - expect(wrapper.find(EuiBadge).prop('color')).toEqual('#40bfff'); - expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: good'); - }); - - it('renders as yellow with the text "standard" for a performance score of less than 21', () => { - setMockValues({ - queryPerformanceScore: 20, - }); - const wrapper = shallow(); - expect(wrapper.find(EuiBadge).prop('color')).toEqual('#fed566'); - expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: standard'); - }); - - it('renders as red with the text "delayed" for a performance score of 21 or more', () => { - setMockValues({ - queryPerformanceScore: 100, - }); - const wrapper = shallow(); - expect(wrapper.find(EuiBadge).prop('color')).toEqual('#ff9173'); - expect(wrapper.find(EuiBadge).children().text()).toEqual('Query performance: delayed'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx deleted file mode 100644 index e3dfddc35d88c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/query_performance/query_performance.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiBadge } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ResultSettingsLogic } from '../result_settings_logic'; - -enum QueryPerformanceRating { - Optimal = 'Optimal', - Good = 'Good', - Standard = 'Standard', - Delayed = 'Delayed', -} - -const QUERY_PERFORMANCE_LABEL = (performanceValue: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformanceLabel', { - defaultMessage: 'Query performance: {performanceValue}', - values: { - performanceValue, - }, - }); - -const QUERY_PERFORMANCE_OPTIMAL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.optimalValue', - { defaultMessage: 'optimal' } -); - -const QUERY_PERFORMANCE_GOOD = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.goodValue', - { defaultMessage: 'good' } -); - -const QUERY_PERFORMANCE_STANDARD = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.standardValue', - { defaultMessage: 'standard' } -); - -const QUERY_PERFORMANCE_DELAYED = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.queryPerformance.delayedValue', - { defaultMessage: 'delayed' } -); - -const badgeText: Record = { - [QueryPerformanceRating.Optimal]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_OPTIMAL), - [QueryPerformanceRating.Good]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_GOOD), - [QueryPerformanceRating.Standard]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_STANDARD), - [QueryPerformanceRating.Delayed]: QUERY_PERFORMANCE_LABEL(QUERY_PERFORMANCE_DELAYED), -}; - -const badgeColors: Record = { - [QueryPerformanceRating.Optimal]: '#59deb4', - [QueryPerformanceRating.Good]: '#40bfff', - [QueryPerformanceRating.Standard]: '#fed566', - [QueryPerformanceRating.Delayed]: '#ff9173', -}; - -const getPerformanceRating = (score: number) => { - switch (true) { - case score < 6: - return QueryPerformanceRating.Optimal; - case score < 11: - return QueryPerformanceRating.Good; - case score < 21: - return QueryPerformanceRating.Standard; - default: - return QueryPerformanceRating.Delayed; - } -}; - -export const QueryPerformance: React.FC = () => { - const { queryPerformanceScore } = useValues(ResultSettingsLogic); - const performanceRating = getPerformanceRating(queryPerformanceScore); - return ( - - {badgeText[performanceRating]} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx deleted file mode 100644 index 440acaf136dda..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; -import { getPageHeaderActions } from '../../../test_helpers'; - -import { ResultSettings } from './result_settings'; -import { ResultSettingsTable } from './result_settings_table'; -import { SampleResponse } from './sample_response'; - -describe('ResultSettings', () => { - const values = { - schema: { - foo: 'text', - }, - dataLoading: false, - stagedUpdates: true, - resultFieldsAtDefaultSettings: false, - }; - - const actions = { - initializeResultSettingsData: jest.fn(), - saveResultSettings: jest.fn(), - confirmResetAllFields: jest.fn(), - clearAllFields: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - const subject = () => shallow(); - - it('renders', () => { - const wrapper = subject(); - expect(wrapper.find(ResultSettingsTable).exists()).toBe(true); - expect(wrapper.find(SampleResponse).exists()).toBe(true); - }); - - it('initializes result settings data when mounted', () => { - shallow(); - expect(actions.initializeResultSettingsData).toHaveBeenCalled(); - }); - - it('renders a "save" button that will save the current changes', () => { - const buttons = getPageHeaderActions(subject()); - expect(buttons.children().length).toBe(3); - const saveButton = buttons.find('[data-test-subj="SaveResultSettings"]'); - saveButton.simulate('click'); - expect(actions.saveResultSettings).toHaveBeenCalled(); - }); - - it('renders the "save" button as disabled if the user has made no changes since the page loaded', () => { - setMockValues({ - ...values, - stagedUpdates: false, - }); - const buttons = getPageHeaderActions(subject()); - const saveButton = buttons.find('[data-test-subj="SaveResultSettings"]'); - expect(saveButton.prop('disabled')).toBe(true); - }); - - it('renders the "save" button as disabled if everything is disabled', () => { - setMockValues({ - ...values, - stagedUpdates: true, - resultFieldsEmpty: true, - }); - const buttons = getPageHeaderActions(subject()); - const saveButton = buttons.find('[data-test-subj="SaveResultSettings"]'); - expect(saveButton.prop('disabled')).toBe(true); - }); - - it('renders a "restore defaults" button that will reset all values to their defaults', () => { - const buttons = getPageHeaderActions(subject()); - expect(buttons.children().length).toBe(3); - const resetButton = buttons.find('[data-test-subj="ResetResultSettings"]'); - resetButton.simulate('click'); - expect(actions.confirmResetAllFields).toHaveBeenCalled(); - }); - - it('renders the "restore defaults" button as disabled if the values are already at their defaults', () => { - setMockValues({ - ...values, - resultFieldsAtDefaultSettings: true, - }); - const buttons = getPageHeaderActions(subject()); - const resetButton = buttons.find('[data-test-subj="ResetResultSettings"]'); - expect(resetButton.prop('disabled')).toBe(true); - }); - - it('renders a "clear" button that will remove all selected options', () => { - const buttons = getPageHeaderActions(subject()); - expect(buttons.children().length).toBe(3); - const clearButton = buttons.find('[data-test-subj="ClearResultSettings"]'); - clearButton.simulate('click'); - expect(actions.clearAllFields).toHaveBeenCalled(); - }); - - it('will prevent user from leaving the page if there are unsaved changes', () => { - setMockValues({ - ...values, - stagedUpdates: true, - }); - expect(subject().find(UnsavedChangesPrompt).prop('hasUnsavedChanges')).toBe(true); - }); - - describe('when there is no schema yet', () => { - let wrapper: ShallowWrapper; - beforeAll(() => { - setMockValues({ - ...values, - schema: {}, - }); - wrapper = subject(); - }); - - it('will not render action buttons', () => { - const buttons = getPageHeaderActions(wrapper); - expect(buttons.children().length).toBe(0); - }); - - it('will render an empty state', () => { - expect(wrapper.prop('isEmptyState')).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx deleted file mode 100644 index 1b9b5670af359..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { SAVE_BUTTON_LABEL } from '../../../shared/constants'; -import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; -import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../constants'; -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { EmptyState } from './components'; -import { RESULT_SETTINGS_TITLE } from './constants'; -import { ResultSettingsTable } from './result_settings_table'; -import { SampleResponse } from './sample_response'; - -import { ResultSettingsLogic } from '.'; - -const CLEAR_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.clearButtonLabel', - { defaultMessage: 'Clear all values' } -); - -const UNSAVED_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.unsavedChangesMessage', - { defaultMessage: 'Result Settings have not been saved. Are you sure you want to leave?' } -); - -export const ResultSettings: React.FC = () => { - const { dataLoading, schema, stagedUpdates, resultFieldsAtDefaultSettings, resultFieldsEmpty } = - useValues(ResultSettingsLogic); - const { - initializeResultSettingsData, - saveResultSettings, - confirmResetAllFields, - clearAllFields, - } = useActions(ResultSettingsLogic); - - useEffect(() => { - initializeResultSettingsData(); - }, []); - - const hasSchema = Object.keys(schema).length > 0; - - return ( - - {SAVE_BUTTON_LABEL} - , - - {RESTORE_DEFAULTS_BUTTON_LABEL} - , - - {CLEAR_BUTTON_LABEL} - , - ] - : [], - }} - isLoading={dataLoading} - isEmptyState={!hasSchema} - emptyState={} - > - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts deleted file mode 100644 index ca2bff383d2b5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ /dev/null @@ -1,1025 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../__mocks__/kea_logic'; -import { mockEngineValues } from '../../__mocks__'; - -import { omit } from 'lodash'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { AdvancedSchema, SchemaConflicts, SchemaType } from '../../../shared/schema/types'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { ServerFieldResultSettingObject } from './types'; - -import { ResultSettingsLogic } from '.'; - -// toHaveBeenCalledWith uses toEqual which is a more lenient check. We have a couple of -// methods that need a stricter check, using `toStrictEqual`. -const expectToHaveBeenCalledWithStrict = ( - mock: jest.Mock, - expectedParam1: string, - expectedParam2: object -) => { - const [param1, param2] = mock.mock.calls[0]; - expect(param1).toEqual(expectedParam1); - expect(param2).toStrictEqual(expectedParam2); -}; - -describe('ResultSettingsLogic', () => { - const { mount } = new LogicMounter(ResultSettingsLogic); - - const DEFAULT_VALUES = { - dataLoading: true, - saving: false, - resultFields: {}, - lastSavedResultFields: {}, - schema: {}, - schemaConflicts: {}, - }; - - const SELECTORS = { - validResultFields: {}, - serverResultFields: {}, - reducedServerResultFields: {}, - resultFieldsEmpty: true, - resultFieldsAtDefaultSettings: true, - stagedUpdates: false, - nonTextResultFields: {}, - textResultFields: {}, - queryPerformanceScore: 0, - }; - - const FUNCTIONAL_SELECTORS = ['isSnippetAllowed']; - - // Values without selectors - const resultSettingLogicValues = () => - omit(ResultSettingsLogic.values, FUNCTIONAL_SELECTORS, Object.keys(SELECTORS)); - - beforeEach(() => { - jest.clearAllMocks(); - mockEngineValues.engineName = 'test-engine'; - }); - - it('has expected default values', () => { - mount(); - expect(omit(ResultSettingsLogic.values, FUNCTIONAL_SELECTORS)).toEqual({ - ...DEFAULT_VALUES, - ...SELECTORS, - }); - }); - - describe('actions', () => { - describe('initializeResultFields', () => { - const serverResultFields: ServerFieldResultSettingObject = { - foo: { raw: { size: 5 } }, - bar: { raw: { size: 5 } }, - }; - const schema: AdvancedSchema = { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - baz: { type: SchemaType.Text, capabilities: {} }, - }; - const schemaConflicts: SchemaConflicts = { - foo: { - text: ['foo'], - number: ['foo'], - geolocation: [], - date: [], - }, - }; - - it('will initialize all result field state within the UI, based on provided server data', () => { - mount({ - dataLoading: true, - saving: true, - }); - - ResultSettingsLogic.actions.initializeResultFields( - serverResultFields, - schema, - schemaConflicts - ); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - saving: false, - // It converts the passed server result fields to a client results field and stores it - // as resultFields - resultFields: { - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - // Baz was not part of the original serverResultFields, it was injected based on the schema - baz: { - raw: false, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }, - // It also saves it as lastSavedResultFields - lastSavedResultFields: { - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - // Baz was not part of the original serverResultFields, it was injected based on the schema - baz: { - raw: false, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }, - // Stores the provided schema details - schema, - schemaConflicts, - }); - }); - - it('default schema conflicts data if none was provided', () => { - mount(); - - ResultSettingsLogic.actions.initializeResultFields(serverResultFields, schema); - - expect(ResultSettingsLogic.values.schemaConflicts).toEqual({}); - }); - }); - - describe('clearAllFields', () => { - it('should remove all settings that have been set for each field', () => { - mount({ - resultFields: { - quuz: { raw: false, snippet: false, snippetFallback: false }, - corge: { raw: true, snippet: false, snippetFallback: true }, - }, - }); - - ResultSettingsLogic.actions.clearAllFields(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - resultFields: { - quuz: {}, - corge: {}, - }, - }); - }); - }); - - describe('resetAllFields', () => { - it('should reset all settings to their default values per field', () => { - mount({ - resultFields: { - quuz: { raw: true, snippet: true, snippetFallback: true }, - corge: { raw: true, snippet: true, snippetFallback: true }, - }, - }); - - ResultSettingsLogic.actions.resetAllFields(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - resultFields: { - quuz: { raw: true, snippet: false, snippetFallback: false }, - corge: { raw: true, snippet: false, snippetFallback: false }, - }, - }); - }); - }); - - describe('updateField', () => { - const initialValues = { - resultFields: { - foo: { raw: true, snippet: true, snippetFallback: true }, - bar: { raw: true, snippet: true, snippetFallback: true }, - }, - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - }, - }; - - it('should update settings for an individual field', () => { - mount({ - ...initialValues, - }); - - ResultSettingsLogic.actions.updateField('foo', { - raw: true, - snippet: false, - snippetFallback: false, - }); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - ...initialValues, - // the settings for foo are updated below for any *ResultFields state in which they appear - resultFields: { - foo: { raw: true, snippet: false, snippetFallback: false }, - bar: { raw: true, snippet: true, snippetFallback: true }, - }, - }); - }); - - it('should do nothing if the specified field does not exist', () => { - mount(initialValues); - - ResultSettingsLogic.actions.updateField('baz', { - raw: false, - snippet: false, - snippetFallback: false, - }); - - // 'baz' does not exist in state, so nothing is updated - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - ...initialValues, - }); - }); - }); - - describe('saving', () => { - it('sets saving to true', () => { - mount({ - saving: false, - }); - - ResultSettingsLogic.actions.saving(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - saving: true, - }); - }); - }); - }); - - describe('selectors', () => { - describe('validResultFields', () => { - it('should filter out nested fields and keep subfields only', () => { - mount({ - schema: { - simple_field: { type: SchemaType.Text, capabilities: {} }, - nested_object: { type: SchemaType.Nested, capabilities: {} }, - 'nested_object.subfield': { type: SchemaType.Text, capabilities: {} }, - 'simple_object.subfield': { type: SchemaType.Number, capabilities: {} }, - }, - resultFields: { - simple_field: { raw: true }, - nested_object: { raw: true }, - 'nested_object.subfield': { raw: true }, - 'simple_object.subfield': { raw: true }, - }, - }); - - expect(ResultSettingsLogic.values.validResultFields).toEqual({ - simple_field: { raw: true }, - 'nested_object.subfield': { raw: true }, - 'simple_object.subfield': { raw: true }, - }); - }); - - it('should filter out field that are missing in the schema', () => { - mount({ - schema: { - simple_field: { type: SchemaType.Text, capabilities: {} }, - }, - resultFields: { - simple_field: { raw: true }, - invalid_field: { raw: true }, - }, - }); - - expect(ResultSettingsLogic.values.validResultFields).toEqual({ - simple_field: { raw: true }, - }); - }); - }); - - describe('textResultFields', () => { - it('should return only resultFields that have a type of "text" in the engine schema', () => { - mount({ - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - baz: { type: SchemaType.Text, capabilities: {} }, - }, - resultFields: { - foo: { raw: true, rawSize: 5 }, - bar: { raw: true, rawSize: 5 }, - baz: { raw: true, rawSize: 5 }, - }, - }); - - expect(ResultSettingsLogic.values.textResultFields).toEqual({ - baz: { raw: true, rawSize: 5 }, - foo: { raw: true, rawSize: 5 }, - }); - }); - }); - - describe('nonTextResultFields', () => { - it('should return only resultFields that have a type other than "text" in the engine schema', () => { - mount({ - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - baz: { type: SchemaType.Text, capabilities: {} }, - }, - resultFields: { - foo: { raw: true, rawSize: 5 }, - bar: { raw: true, rawSize: 5 }, - baz: { raw: true, rawSize: 5 }, - }, - }); - - expect(ResultSettingsLogic.values.nonTextResultFields).toEqual({ - bar: { raw: true, rawSize: 5 }, - }); - }); - }); - - describe('isSnippetAllowed', () => { - it('should return true if field have the snippet capability', () => { - mount({ - schema: { - foo: { type: SchemaType.Text, capabilities: { snippet: true } }, - }, - }); - - expect(ResultSettingsLogic.values.isSnippetAllowed('foo')).toEqual(true); - }); - - it('should return false otherwise', () => { - mount({ - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - }, - }); - expect(ResultSettingsLogic.values.isSnippetAllowed('foo')).toEqual(false); - }); - }); - - describe('resultFieldsAtDefaultSettings', () => { - it('should return true if all fields are at their default settings', () => { - mount({ - resultFields: { - foo: { raw: true, snippet: false, snippetFallback: false }, - bar: { raw: true, snippet: false, snippetFallback: false }, - }, - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Text, capabilities: {} }, - }, - }); - - expect(ResultSettingsLogic.values.resultFieldsAtDefaultSettings).toEqual(true); - }); - - it('should return false otherwise', () => { - mount({ - resultFields: { - foo: { raw: true, snippet: false, snippetFallback: false }, - bar: { raw: true, snippet: true, snippetFallback: false }, - }, - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Text, capabilities: {} }, - }, - }); - - expect(ResultSettingsLogic.values.resultFieldsAtDefaultSettings).toEqual(false); - }); - }); - - describe('resultFieldsEmpty', () => { - it('should return true if no raw or snippet fields are enabled', () => { - mount({ - resultFields: { - foo: { raw: false }, - bar: {}, - baz: { raw: false, snippet: false }, - }, - }); - - expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(true); - }); - }); - - describe('stagedUpdates', () => { - it('should return true if changes have been made since the last save', () => { - mount({ - lastSavedResultFields: { - foo: {}, - bar: { raw: true, snippet: true, snippetFallback: false }, - }, - resultFields: { - foo: { raw: false, snippet: true, snippetFallback: true }, - bar: { raw: true, snippet: true, snippetFallback: false }, - }, - }); - - // resultFields is different than lastSavedResultsFields, which happens if changes - // have been made since the last save, which is represented by lastSavedResultFields - expect(ResultSettingsLogic.values.stagedUpdates).toEqual(true); - }); - - it('should return false otherwise', () => { - mount({ - lastSavedResultFields: { - foo: { raw: false, snippet: true, snippetFallback: true }, - bar: { raw: true, snippet: true, snippetFallback: false }, - }, - resultFields: { - foo: { raw: false, snippet: true, snippetFallback: true }, - bar: { raw: true, snippet: true, snippetFallback: false }, - }, - }); - - expect(ResultSettingsLogic.values.stagedUpdates).toEqual(false); - }); - }); - - describe('serverResultFields', () => { - it('returns resultFields formatted for the server', () => { - mount({ - resultFields: { - foo: { - raw: true, - rawSize: 5, - snippet: true, - snippetFallback: true, - snippetSize: 3, - }, - bar: {}, - baz: { - raw: false, - snippet: false, - snippetFallback: false, - }, - }, - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - baz: { type: SchemaType.Text, capabilities: {} }, - }, - }); - - expect(ResultSettingsLogic.values.serverResultFields).toEqual({ - foo: { - raw: { size: 5 }, - snippet: { fallback: true, size: 3 }, - }, - bar: {}, - baz: {}, - }); - }); - }); - - describe('reducedServerResultFields', () => { - it('returns server formatted fields with empty settings filtered out', () => { - mount({ - resultFields: { - foo: { - raw: true, - rawSize: 5, - }, - bar: {}, - }, - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - }, - }); - - expect(ResultSettingsLogic.values.reducedServerResultFields).toEqual({ - // bar was filtered out because it has neither raw nor snippet data set - foo: { raw: { size: 5 } }, - }); - }); - }); - - describe('queryPerformanceScore', () => { - describe('returns a score for the current query performance based on the result settings', () => { - it('considers a text value with raw set (but no size) as worth 1.5', () => { - mount({ - resultFields: { foo: { raw: true } }, - schema: { foo: { type: SchemaType.Text, capabilities: {} } }, - }); - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(1.5); - }); - - it('considers a text value with raw set and a size over 250 as also worth 1.5', () => { - mount({ - resultFields: { foo: { raw: true, rawSize: 251 } }, - schema: { foo: { type: SchemaType.Text, capabilities: {} } }, - }); - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(1.5); - }); - - it('considers a text value with raw set and a size less than or equal to 250 as worth 1', () => { - mount({ - resultFields: { foo: { raw: true, rawSize: 250 } }, - schema: { foo: { type: SchemaType.Text, capabilities: {} } }, - }); - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(1); - }); - - it('considers a text value with a snippet set as worth 2', () => { - mount({ - resultFields: { foo: { snippet: true, snippetSize: 50, snippetFallback: true } }, - schema: { foo: { type: SchemaType.Text, capabilities: {} } }, - }); - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(2); - }); - - it('will sum raw and snippet values if both are set', () => { - mount({ - resultFields: { foo: { snippet: true, raw: true } }, - schema: { foo: { type: SchemaType.Text, capabilities: {} } }, - }); - // 1.5 (raw) + 2 (snippet) = 3.5 - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(3.5); - }); - - it('considers a non-text value with raw set as 0.2', () => { - mount({ - resultFields: { foo: { raw: true } }, - schema: { foo: { type: SchemaType.Number, capabilities: {} } }, - }); - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(0.2); - }); - - it('can sum variations of all the prior', () => { - mount({ - resultFields: { - foo: { raw: true }, - bar: { raw: true, snippet: true }, - baz: { raw: true }, - }, - schema: { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - baz: { type: SchemaType.Text, capabilities: {} }, - }, - }); - // 1.5 (foo) + 3.5 (bar) + baz (.2) = 5.2 - expect(ResultSettingsLogic.values.queryPerformanceScore).toEqual(5.2); - }); - }); - }); - }); - - describe('listeners', () => { - const { http } = mockHttpValues; - let confirmSpy: jest.SpyInstance; - - beforeAll(() => { - confirmSpy = jest.spyOn(window, 'confirm'); - }); - afterAll(() => confirmSpy.mockRestore()); - - const serverFieldResultSettings = { - foo: { - raw: {}, - }, - bar: { - raw: {}, - }, - }; - const schema = { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - }; - const schemaConflicts = { - baz: { - text: ['test'], - number: ['test2'], - }, - }; - - describe('clearRawSizeForField', () => { - it('should remove the raw size set on a field', () => { - mount({ - resultFields: { - foo: { raw: true, rawSize: 5, snippet: false }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.clearRawSizeForField('foo'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'foo', - { - raw: true, - snippet: false, - } - ); - }); - }); - - describe('clearSnippetSizeForField', () => { - it('should remove the snippet size set on a field', () => { - mount({ - resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.clearSnippetSizeForField('foo'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'foo', - { - raw: false, - snippet: true, - } - ); - }); - }); - - describe('toggleRawForField', () => { - it('should toggle the raw value on for a field', () => { - mount({ - resultFields: { - bar: { raw: false, snippet: false }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleRawForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: true, - snippet: false, - } - ); - }); - - it('should maintain rawSize if it was set prior', () => { - mount({ - resultFields: { - bar: { raw: false, rawSize: 10, snippet: false }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleRawForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: true, - rawSize: 10, - snippet: false, - } - ); - }); - - it('should remove rawSize value when toggling off', () => { - mount({ - resultFields: { - bar: { raw: true, rawSize: 5, snippet: false }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleRawForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: false, - snippet: false, - } - ); - }); - - it('should still work if the object is empty', () => { - mount({ - resultFields: { - bar: {}, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleRawForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: true, - } - ); - }); - }); - - describe('toggleSnippetForField', () => { - it('should toggle the raw value on for a field, always setting the snippet size to 100', () => { - mount({ - resultFields: { - bar: { raw: false, snippet: false }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleSnippetForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: false, - snippet: true, - snippetSize: 100, - } - ); - }); - - it('should remove rawSize and snippetFallback value when toggling off', () => { - mount({ - resultFields: { - bar: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleSnippetForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: false, - snippet: false, - snippetFallback: false, - } - ); - }); - - it('should still work if the object is empty', () => { - mount({ - resultFields: { - bar: {}, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleSnippetForField('bar'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - snippet: true, - snippetSize: 100, - } - ); - }); - }); - - describe('toggleSnippetFallbackForField', () => { - it('should toggle the snippetFallback value for a field', () => { - mount({ - resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.toggleSnippetFallbackForField('foo'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'foo', - { - raw: false, - snippet: true, - snippetSize: 5, - snippetFallback: false, - } - ); - }); - }); - - describe('updateRawSizeForField', () => { - it('should update the rawSize value for a field', () => { - mount({ - resultFields: { - bar: { raw: true, rawSize: 5, snippet: false }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.updateRawSizeForField('bar', 7); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'bar', - { - raw: true, - rawSize: 7, - snippet: false, - } - ); - }); - }); - - describe('updateSnippetSizeForField', () => { - it('should update the snippetSize value for a field', () => { - mount({ - resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.updateSnippetSizeForField('foo', 7); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'foo', - { - raw: false, - snippet: true, - snippetSize: 7, - snippetFallback: true, - } - ); - }); - }); - - describe('initializeResultSettingsData', () => { - it('should remove the snippet size set on a field', () => { - mount({ - resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, - }, - }); - jest.spyOn(ResultSettingsLogic.actions, 'updateField'); - - ResultSettingsLogic.actions.clearSnippetSizeForField('foo'); - - expectToHaveBeenCalledWithStrict( - ResultSettingsLogic.actions.updateField as jest.Mock, - 'foo', - { - raw: false, - snippet: true, - } - ); - }); - }); - - describe('initializeResultFields', () => { - it('should make an API call and set state based on the response', async () => { - mount(); - http.get.mockReturnValueOnce( - Promise.resolve({ - searchSettings: { - result_fields: serverFieldResultSettings, - }, - schema, - schemaConflicts, - }) - ); - jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields'); - - ResultSettingsLogic.actions.initializeResultSettingsData(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/result_settings/details' - ); - expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith( - serverFieldResultSettings, - schema, - schemaConflicts - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - ResultSettingsLogic.actions.initializeResultSettingsData(); - }); - }); - - describe('confirmResetAllFields', () => { - it('will reset all fields as long as the user confirms the action', async () => { - mount(); - confirmSpy.mockImplementation(() => true); - jest.spyOn(ResultSettingsLogic.actions, 'resetAllFields'); - - ResultSettingsLogic.actions.confirmResetAllFields(); - - expect(ResultSettingsLogic.actions.resetAllFields).toHaveBeenCalled(); - }); - - it('will do nothing if the user cancels the action', async () => { - mount(); - confirmSpy.mockImplementation(() => false); - jest.spyOn(ResultSettingsLogic.actions, 'resetAllFields'); - - ResultSettingsLogic.actions.confirmResetAllFields(); - - expect(ResultSettingsLogic.actions.resetAllFields).not.toHaveBeenCalled(); - }); - }); - - describe('saveResultSettings', () => { - beforeEach(() => { - confirmSpy.mockImplementation(() => true); - }); - - it('should make an API call to update result settings and update state accordingly', async () => { - const resultFields = { - foo: { raw: true, rawSize: 100 }, - }; - - const serverResultFields = { - foo: { raw: { size: 100 } }, - }; - - mount({ - schema, - resultFields, - }); - http.put.mockReturnValueOnce( - Promise.resolve({ - result_fields: serverResultFields, - }) - ); - jest.spyOn(ResultSettingsLogic.actions, 'saving'); - jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields'); - - ResultSettingsLogic.actions.saveResultSettings(); - - expect(ResultSettingsLogic.actions.saving).toHaveBeenCalled(); - - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/test-engine/result_settings', - { - body: JSON.stringify({ - result_fields: serverResultFields, - }), - } - ); - expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith( - serverResultFields, - schema - ); - }); - - itShowsServerErrorAsFlashMessage(http.put, () => { - mount(); - ResultSettingsLogic.actions.saveResultSettings(); - }); - - it('does nothing if the user does not confirm', async () => { - mount(); - confirmSpy.mockImplementation(() => false); - - ResultSettingsLogic.actions.saveResultSettings(); - await nextTick(); - - expect(http.put).not.toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts deleted file mode 100644 index 481baca5e35d4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts +++ /dev/null @@ -1,378 +0,0 @@ -/* - * 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 { omit, isEqual } from 'lodash'; - -import { i18n } from '@kbn/i18n'; - -import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { AdvancedSchema, SchemaConflicts, SchemaType } from '../../../shared/schema/types'; -import { EngineLogic } from '../engine'; - -import { DEFAULT_SNIPPET_SIZE } from './constants'; -import { - FieldResultSetting, - FieldResultSettingObject, - ServerFieldResultSetting, - ServerFieldResultSettingObject, -} from './types'; - -import { - areFieldsAtDefaultSettings, - areFieldsEmpty, - clearAllFields, - convertServerResultFieldsToResultFields, - convertToServerFieldResultSetting, - resetAllFields, - splitResultFields, -} from './utils'; - -interface ResultSettingsActions { - initializeResultFields( - serverResultFields: ServerFieldResultSettingObject, - schema: AdvancedSchema, - schemaConflicts?: SchemaConflicts - ): { - resultFields: FieldResultSettingObject; - schema: AdvancedSchema; - schemaConflicts: SchemaConflicts; - }; - clearAllFields(): void; - resetAllFields(): void; - updateField( - fieldName: string, - settings: FieldResultSetting | {} - ): { fieldName: string; settings: FieldResultSetting }; - saving(): void; - // Listeners - clearRawSizeForField(fieldName: string): { fieldName: string }; - clearSnippetSizeForField(fieldName: string): { fieldName: string }; - toggleRawForField(fieldName: string): { fieldName: string }; - toggleSnippetForField(fieldName: string): { fieldName: string }; - toggleSnippetFallbackForField(fieldName: string): { fieldName: string }; - updateRawSizeForField(fieldName: string, size: number): { fieldName: string; size: number }; - updateSnippetSizeForField(fieldName: string, size: number): { fieldName: string; size: number }; - initializeResultSettingsData(): void; - confirmResetAllFields(): void; - saveResultSettings(): void; -} - -interface ResultSettingsValues { - dataLoading: boolean; - saving: boolean; - resultFields: FieldResultSettingObject; - lastSavedResultFields: FieldResultSettingObject; - schema: AdvancedSchema; - schemaConflicts: SchemaConflicts; - // Selectors - validResultFields: FieldResultSettingObject; - textResultFields: FieldResultSettingObject; - nonTextResultFields: FieldResultSettingObject; - serverResultFields: ServerFieldResultSettingObject; - resultFieldsAtDefaultSettings: boolean; - resultFieldsEmpty: boolean; - stagedUpdates: true; - reducedServerResultFields: ServerFieldResultSettingObject; - queryPerformanceScore: number; - isSnippetAllowed: (fieldName: string) => boolean; -} - -const SAVE_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmSaveMessage', - { - defaultMessage: - 'The changes will start immediately. Make sure your applications are ready to accept the new search results!', - } -); - -const RESET_CONFIRMATION_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmResetMessage', - { - defaultMessage: - 'Are you sure you want to restore result settings defaults? This will set all fields back to raw with no limits.', - } -); - -export const ResultSettingsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'result_settings_logic'], - actions: () => ({ - initializeResultFields: (serverResultFields, schema, schemaConflicts) => { - const resultFields = convertServerResultFieldsToResultFields(serverResultFields, schema); - - return { - resultFields, - schema, - schemaConflicts, - }; - }, - clearAllFields: () => true, - resetAllFields: () => true, - updateField: (fieldName, settings) => ({ fieldName, settings }), - saving: () => true, - clearRawSizeForField: (fieldName) => ({ fieldName }), - clearSnippetSizeForField: (fieldName) => ({ fieldName }), - toggleRawForField: (fieldName) => ({ fieldName }), - toggleSnippetForField: (fieldName) => ({ fieldName }), - toggleSnippetFallbackForField: (fieldName) => ({ fieldName }), - updateRawSizeForField: (fieldName, size) => ({ fieldName, size }), - updateSnippetSizeForField: (fieldName, size) => ({ fieldName, size }), - initializeResultSettingsData: () => true, - confirmResetAllFields: () => true, - saveResultSettings: () => true, - }), - reducers: () => ({ - dataLoading: [ - true, - { - initializeResultFields: () => false, - }, - ], - saving: [ - false, - { - initializeResultFields: () => false, - saving: () => true, - }, - ], - resultFields: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - initializeResultFields: (_, { resultFields }) => resultFields, - // @ts-expect-error upgrade typescript v5.1.6 - clearAllFields: (resultFields) => clearAllFields(resultFields), - // @ts-expect-error upgrade typescript v5.1.6 - resetAllFields: (resultFields) => resetAllFields(resultFields), - // @ts-expect-error upgrade typescript v5.1.6 - updateField: (resultFields, { fieldName, settings }) => - Object.hasOwn(resultFields, fieldName) - ? { ...resultFields, [fieldName]: settings } - : resultFields, - }, - ], - lastSavedResultFields: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - initializeResultFields: (_, { resultFields }) => resultFields, - }, - ], - schema: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - initializeResultFields: (_, { schema }) => schema, - }, - ], - schemaConflicts: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - initializeResultFields: (_, { schemaConflicts }) => schemaConflicts || {}, - }, - ], - }), - selectors: ({ selectors }) => ({ - validResultFields: [ - () => [selectors.resultFields, selectors.schema], - (resultFields: FieldResultSettingObject, schema: AdvancedSchema): FieldResultSettingObject => - Object.entries(resultFields).reduce((validResultFields, [fieldName, fieldSettings]) => { - if (!schema[fieldName] || schema[fieldName].type === SchemaType.Nested) { - return validResultFields; - } - return { ...validResultFields, [fieldName]: fieldSettings }; - }, {}), - ], - textResultFields: [ - () => [selectors.validResultFields, selectors.schema], - (resultFields: FieldResultSettingObject, schema: AdvancedSchema) => { - const { textResultFields } = splitResultFields(resultFields, schema); - return textResultFields; - }, - ], - nonTextResultFields: [ - () => [selectors.validResultFields, selectors.schema], - (resultFields: FieldResultSettingObject, schema: AdvancedSchema) => { - const { nonTextResultFields } = splitResultFields(resultFields, schema); - return nonTextResultFields; - }, - ], - isSnippetAllowed: [ - () => [selectors.schema], - (schema: AdvancedSchema) => { - return (fieldName: string): boolean => !!schema[fieldName]?.capabilities.snippet; - }, - ], - serverResultFields: [ - () => [selectors.validResultFields], - (resultFields: FieldResultSettingObject) => { - return Object.entries(resultFields).reduce((serverResultFields, [fieldName, settings]) => { - return { - ...serverResultFields, - [fieldName]: convertToServerFieldResultSetting(settings as FieldResultSetting), - }; - }, {}); - }, - ], - resultFieldsAtDefaultSettings: [ - () => [selectors.validResultFields], - (resultFields) => areFieldsAtDefaultSettings(resultFields), - ], - resultFieldsEmpty: [ - () => [selectors.validResultFields], - (resultFields) => areFieldsEmpty(resultFields), - ], - stagedUpdates: [ - () => [selectors.lastSavedResultFields, selectors.resultFields], - (lastSavedResultFields, resultFields) => !isEqual(lastSavedResultFields, resultFields), - ], - reducedServerResultFields: [ - () => [selectors.serverResultFields], - (serverResultFields: ServerFieldResultSettingObject) => - Object.entries(serverResultFields).reduce( - (acc: ServerFieldResultSettingObject, [fieldName, resultSetting]) => { - if (resultSetting.raw || resultSetting.snippet) { - acc[fieldName] = resultSetting; - } - return acc; - }, - {} - ), - ], - queryPerformanceScore: [ - () => [selectors.serverResultFields, selectors.schema], - (serverResultFields: ServerFieldResultSettingObject, schema: AdvancedSchema) => { - return Object.entries(serverResultFields).reduce((acc, [fieldName, resultField]) => { - let newAcc = acc; - if (resultField.raw) { - if (schema[fieldName].type !== SchemaType.Text) { - newAcc += 0.2; - } else if ( - typeof resultField.raw === 'object' && - resultField.raw.size && - resultField.raw.size <= 250 - ) { - newAcc += 1.0; - } else { - newAcc += 1.5; - } - } - if (resultField.snippet) { - newAcc += 2.0; - } - return newAcc; - }, 0); - }, - ], - }), - listeners: ({ actions, values }) => ({ - clearRawSizeForField: ({ fieldName }) => { - actions.updateField(fieldName, omit(values.resultFields[fieldName], ['rawSize'])); - }, - clearSnippetSizeForField: ({ fieldName }) => { - actions.updateField(fieldName, omit(values.resultFields[fieldName], ['snippetSize'])); - }, - toggleRawForField: ({ fieldName }) => { - // We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely - const field = values.resultFields[fieldName] as FieldResultSetting; - const raw = !field.raw; - - actions.updateField(fieldName, { - ...omit(field, ['rawSize']), - raw, - ...(raw && field.rawSize ? { rawSize: field.rawSize } : {}), - }); - }, - toggleSnippetForField: ({ fieldName }) => { - // We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely - const field = values.resultFields[fieldName] as FieldResultSetting; - const snippet = !field.snippet; - - actions.updateField(fieldName, { - ...omit(field, ['snippetSize']), - snippet, - ...(snippet ? { snippetSize: DEFAULT_SNIPPET_SIZE } : { snippetFallback: false }), - }); - }, - toggleSnippetFallbackForField: ({ fieldName }) => { - // We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely - const field = values.resultFields[fieldName] as FieldResultSetting; - actions.updateField(fieldName, { - ...field, - snippetFallback: !field.snippetFallback, - }); - }, - updateRawSizeForField: ({ fieldName, size }) => { - actions.updateField(fieldName, { ...values.resultFields[fieldName], rawSize: size }); - }, - updateSnippetSizeForField: ({ fieldName, size }) => { - actions.updateField(fieldName, { ...values.resultFields[fieldName], snippetSize: size }); - }, - initializeResultSettingsData: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const url = `/internal/app_search/engines/${engineName}/result_settings/details`; - - try { - const { - schema, - schemaConflicts, - searchSettings: { result_fields: serverFieldResultSettings }, - } = await http.get<{ - schema: AdvancedSchema; - schemaConflicts?: SchemaConflicts; - searchSettings: { result_fields: Record }; - }>(url); - - actions.initializeResultFields(serverFieldResultSettings, schema, schemaConflicts); - } catch (e) { - flashAPIErrors(e); - } - }, - confirmResetAllFields: () => { - if (window.confirm(RESET_CONFIRMATION_MESSAGE)) { - actions.resetAllFields(); - } - }, - saveResultSettings: async () => { - if (window.confirm(SAVE_CONFIRMATION_MESSAGE)) { - actions.saving(); - - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const url = `/internal/app_search/engines/${engineName}/result_settings`; - - actions.saving(); - - try { - const response = await http.put<{ - result_fields: Record; - }>(url, { - body: JSON.stringify({ - result_fields: values.reducedServerResultFields, - }), - }); - - actions.initializeResultFields(response.result_fields, values.schema); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage', - { - defaultMessage: 'Result settings were saved', - } - ) - ); - } catch (e) { - flashAPIErrors(e); - } - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/column_headers.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/column_headers.test.tsx deleted file mode 100644 index a2ef43908776e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/column_headers.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { EuiTableHeaderCell } from '@elastic/eui'; - -import { ColumnHeaders } from './column_headers'; - -describe('ColumnHeaders', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiTableHeaderCell).length).toBe(3); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/column_headers.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/column_headers.tsx deleted file mode 100644 index 8bc4da27dcbb7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/column_headers.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { EuiIconTip, EuiTableHeader, EuiTableHeaderCell } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const ColumnHeaders: React.FC = () => { - return ( - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawTitle', { - defaultMessage: 'Raw', - })} - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.highlightingTitle', - { - defaultMessage: 'Highlighting', - } - )} - tags for highlighting. Fallback will look for a snippet match, but fallback to an escaped raw value if none is found. Range is between 20-1000. Defaults to 100.', - ignoreTag: true, - } - )} - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx deleted file mode 100644 index 580bb4db76389..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiTableRow } from '@elastic/eui'; - -import { DisabledFieldsBody } from './disabled_fields_body'; - -describe('DisabledFieldsBody', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues({ - schemaConflicts: { - foo: { - text: ['engine1'], - number: ['engine2'], - }, - bar: { - text: ['engine1'], - number: ['engine2'], - }, - }, - }); - }); - - it('renders a table row for each field', () => { - const wrapper = shallow(); - const tableRows = wrapper.find(EuiTableRow); - - expect(tableRows.length).toBe(2); - expect(tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( - 'foo' - ); - expect(tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').dive().text()).toEqual( - 'bar' - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx deleted file mode 100644 index 2f4ba0892784d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_body.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiTableRow, EuiTableRowCell, EuiText, EuiHealth } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ResultSettingsLogic } from '..'; - -export const DisabledFieldsBody: React.FC = () => { - const { schemaConflicts } = useValues(ResultSettingsLogic); - return ( - <> - {Object.keys(schemaConflicts).map((fieldName) => ( - - - - {fieldName} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.fieldTypeConflictText', - { - defaultMessage: 'Field-type conflict', - } - )} - - - - ))} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx deleted file mode 100644 index 5237f9ae15c11..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { DisabledFieldsHeader } from './disabled_fields_header'; - -describe('DisabledFieldsHeader', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx deleted file mode 100644 index 1c0c1da3e4ef2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/disabled_fields_header.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const DisabledFieldsHeader: React.FC = () => { - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.disabledFieldsTitle', - { defaultMessage: 'Disabled fields' } - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx deleted file mode 100644 index c012167f67818..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.test.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { EuiFieldNumber } from '@elastic/eui'; - -import { FieldResultSetting } from '../types'; - -import { FieldNumber } from './field_number'; - -describe('FieldNumber', () => { - const fieldSettings = { - raw: true, - rawSize: 29, - snippet: true, - snippetFallback: true, - snippetSize: 15, - }; - - const props = { - fieldSettings, - fieldName: 'foo', - fieldEnabledProperty: 'raw' as keyof FieldResultSetting, - fieldSizeProperty: 'rawSize' as keyof FieldResultSetting, - updateAction: jest.fn(), - clearAction: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('is rendered with its value set from [fieldSizeProperty] in fieldSettings', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(29); - }); - - it('has no value if [fieldSizeProperty] in fieldSettings has no value', () => { - const wrapper = shallow( - - ); - expect(wrapper.find(EuiFieldNumber).prop('value')).toEqual(' '); - }); - - it('is disabled if the [fieldEnabledProperty] in fieldSettings is false', () => { - const wrapper = shallow( - - ); - expect(wrapper.find(EuiFieldNumber).prop('disabled')).toEqual(true); - }); - - it('will call updateAction when the value is changed', () => { - const wrapper = shallow(); - wrapper.simulate('change', { target: { value: '21' } }); - expect(props.updateAction).toHaveBeenCalledWith('foo', 21); - }); - - it('will call clearAction when the value is changed', () => { - const wrapper = shallow(); - wrapper.simulate('change', { target: { value: '' } }); - expect(props.clearAction).toHaveBeenCalledWith('foo'); - }); - - it('will call updateAction on blur', () => { - const wrapper = shallow(); - wrapper.simulate('blur', { target: { value: '21' } }); - expect(props.updateAction).toHaveBeenCalledWith('foo', 21); - }); - - it('will call clearAction on blur if the current value is something other than a number', () => { - const wrapper = shallow(); - wrapper.simulate('blur', { target: { value: '' } }); - expect(props.clearAction).toHaveBeenCalledWith('foo'); - }); - - it('will call updateAction on blur using the minimum possible value if the value is something lower than the minimum', () => { - const wrapper = shallow(); - wrapper.simulate('blur', { target: { value: 5 } }); - expect(props.updateAction).toHaveBeenCalledWith('foo', 20); - }); - - it('will call updateAction on blur using the maximum possible value if the value is something above than the maximum', () => { - const wrapper = shallow(); - wrapper.simulate('blur', { target: { value: 2000 } }); - expect(props.updateAction).toHaveBeenCalledWith('foo', 1000); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx deleted file mode 100644 index f16bab5234ab1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/field_number.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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, { ChangeEvent, FocusEvent } from 'react'; - -import { EuiFieldNumber } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SIZE_FIELD_MAXIMUM, SIZE_FIELD_MINIMUM } from '../constants'; -import { FieldResultSetting } from '../types'; - -const updateOrClearSizeForField = ( - fieldName: string, - fieldValue: number, - updateAction: (fieldName: string, size: number) => void, - clearAction: (fieldName: string) => void -) => { - if (typeof fieldValue === 'number' && !isNaN(fieldValue)) { - updateAction(fieldName, fieldValue); - } else { - clearAction(fieldName); - } -}; - -const handleFieldNumberChange = ( - fieldName: string, - updateAction: (fieldName: string, size: number) => void, - clearAction: (fieldName: string) => void -) => { - return (e: ChangeEvent) => { - const fieldValue = parseInt(e.target.value, 10); - updateOrClearSizeForField(fieldName, fieldValue, updateAction, clearAction); - }; -}; - -const handleFieldNumberBlur = ( - fieldName: string, - updateAction: (fieldName: string, size: number) => void, - clearAction: (fieldName: string) => void -) => { - return (e: FocusEvent) => { - let fieldValue = parseInt(e.target.value, 10); - if (!isNaN(fieldValue)) { - fieldValue = Math.min(SIZE_FIELD_MAXIMUM, Math.max(SIZE_FIELD_MINIMUM, fieldValue)); - } - updateOrClearSizeForField(fieldName, fieldValue, updateAction, clearAction); - }; -}; - -interface Props { - fieldSettings: Partial; - fieldName: string; - fieldEnabledProperty: keyof FieldResultSetting; - fieldSizeProperty: keyof FieldResultSetting; - updateAction: (fieldName: string, size: number) => void; - clearAction: (fieldName: string) => void; -} - -export const FieldNumber: React.FC = ({ - fieldSettings, - fieldName, - fieldEnabledProperty, - fieldSizeProperty, - updateAction, - clearAction, -}) => { - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts deleted file mode 100644 index 1fcbf05bcc0c7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { ResultSettingsTable } from './result_settings_table'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx deleted file mode 100644 index 8b481d38d23bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiTableRow } from '@elastic/eui'; - -import { NonTextFieldsBody } from './non_text_fields_body'; - -describe('NonTextFieldsBody', () => { - const values = { - nonTextResultFields: { - foo: { - raw: false, - }, - zoo: { - raw: true, - rawSize: 5, - }, - bar: { - raw: true, - rawSize: 5, - }, - }, - }; - - const actions = { - toggleRawForField: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - const getTableRows = (wrapper: ShallowWrapper) => wrapper.find(EuiTableRow); - const getBarTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(0); - - it('renders a table row for each field, sorted by field name', () => { - const wrapper = shallow(); - const tableRows = getTableRows(wrapper); - - expect(tableRows.length).toBe(3); - expect( - tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').render().text() - ).toEqual('bar'); - expect( - tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').render().text() - ).toEqual('foo'); - expect( - tableRows.at(2).find('[data-test-subj="ResultSettingFieldName"]').render().text() - ).toEqual('zoo'); - }); - - describe('the "raw" checkbox within each table row', () => { - const getRawCheckbox = () => { - const wrapper = shallow(); - const tableRow = getBarTableRow(wrapper); - return tableRow.find('[data-test-subj="ResultSettingRawCheckBox"]'); - }; - - it('is rendered with its checked property set from state', () => { - const rawCheckbox = getRawCheckbox(); - expect(rawCheckbox.prop('checked')).toEqual(values.nonTextResultFields.bar.raw); - }); - - it("calls 'toggleRawForField' when it is clicked by a user", () => { - const rawCheckbox = getRawCheckbox(); - rawCheckbox.simulate('change'); - expect(actions.toggleRawForField).toHaveBeenCalledWith('bar'); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx deleted file mode 100644 index dc91b5039a3c9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_body.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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, { useMemo } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiTableRow, EuiTableRowCell, EuiCheckbox, EuiTableRowCellCheckbox } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ResultSettingsLogic } from '..'; -import { FieldResultSetting } from '../types'; - -export const NonTextFieldsBody: React.FC = () => { - const { nonTextResultFields } = useValues(ResultSettingsLogic); - const { toggleRawForField } = useActions(ResultSettingsLogic); - - const resultSettingsArray: Array<[string, Partial]> = useMemo(() => { - return Object.entries(nonTextResultFields).sort(([aFieldName], [bFieldName]) => - aFieldName > bFieldName ? 1 : -1 - ); - }, [nonTextResultFields]); - - return ( - <> - {resultSettingsArray.map(([fieldName, fieldSettings]) => ( - - - {fieldName} - - - { - toggleRawForField(fieldName); - }} - /> - - - - ))} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx deleted file mode 100644 index 3c0920f29f884..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { NonTextFieldsHeader } from './non_text_fields_header'; - -describe('NonTextFieldsHeader', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx deleted file mode 100644 index b929187780e10..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/non_text_fields_header.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -export const NonTextFieldsHeader: React.FC = () => { - return ( - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.nonTextFieldsTitle', - { defaultMessage: 'Non-text fields' } - )} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle', - { defaultMessage: 'Raw' } - )} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss deleted file mode 100644 index 7779abe7eb6fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.scss +++ /dev/null @@ -1,30 +0,0 @@ -.resultSettingsTable { - table-layout: auto; - - &__columnLabels { - .euiTableHeaderCell { - border-bottom: none; - padding-bottom: $euiSizeM; - } - } - - .euiTableRow:last-of-type { - .euiTableRowCell, .euiTableRowCellCheckbox { - // Because we are using the large border-top as a way of spacing, and table borders - // are collapsed, these tables do not have a bottom border. The exception to that is - // if the tbody is the last tbody of the table. To make it consistent, we explicitly - // disable all bottom borders here - border-bottom: none; - } - } - - tbody + &__subHeader { - // Since this table has multiple sets of thead + tbody's, this is our way of keeping - // vertical space between the two - border-top: solid $euiSizeL * 2 transparent; - } - - .euiTableRowCellCheckbox .euiTableCellContent { - justify-content: center; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx deleted file mode 100644 index 332b4a31895f4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { DisabledFieldsHeader } from './disabled_fields_header'; -import { NonTextFieldsBody } from './non_text_fields_body'; -import { ResultSettingsTable } from './result_settings_table'; -import { TextFieldsBody } from './text_fields_body'; - -describe('ResultSettingsTable', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues({ - textResultFields: { foo: { raw: true, rawSize: 5, snippet: false, snippetFallback: false } }, - nonTextResultFields: { - bar: { raw: true, rawSize: 5, snippet: false, snippetFallback: false }, - }, - schemaConflicts: { - foo: { - text: ['foo'], - number: ['foo'], - geolocation: [], - date: [], - }, - }, - }); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(TextFieldsBody).exists()).toBe(true); - expect(wrapper.find(NonTextFieldsBody).exists()).toBe(true); - expect(wrapper.find(DisabledFieldsHeader).exists()).toBe(true); - }); - - it('will hide sections that have no data available to show', () => { - setMockValues({ - textResultFields: {}, - nonTextResultFields: {}, - schemaConflicts: {}, - }); - - const wrapper = shallow(); - expect(wrapper.find(TextFieldsBody).exists()).toBe(false); - expect(wrapper.find(NonTextFieldsBody).exists()).toBe(false); - expect(wrapper.find(DisabledFieldsHeader).exists()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx deleted file mode 100644 index eb9ef02cea45e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/result_settings_table.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { EuiTable, EuiTableBody } from '@elastic/eui'; - -import { ResultSettingsLogic } from '..'; - -import { ColumnHeaders } from './column_headers'; -import { DisabledFieldsBody } from './disabled_fields_body'; -import { DisabledFieldsHeader } from './disabled_fields_header'; -import { NonTextFieldsBody } from './non_text_fields_body'; -import { NonTextFieldsHeader } from './non_text_fields_header'; -import { TextFieldsBody } from './text_fields_body'; -import { TextFieldsHeader } from './text_fields_header'; - -import './result_settings_table.scss'; - -export const ResultSettingsTable: React.FC = () => { - const { schemaConflicts, textResultFields, nonTextResultFields } = useValues(ResultSettingsLogic); - - // TODO This table currently has mutiple theads, which is invalid html. We could change these subheaders to be EuiTableRow instead of EuiTableHeader - // to alleviate the issue. - return ( - - - {!!Object.keys(textResultFields).length && ( - - - - - )} - {!!Object.keys(nonTextResultFields).length && ( - - - - - )} - {!!Object.keys(schemaConflicts).length && ( - - - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx deleted file mode 100644 index 3986809c549aa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.test.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiTableRow } from '@elastic/eui'; - -import { TextFieldsBody } from './text_fields_body'; - -describe('TextFieldsBody', () => { - const values = { - textResultFields: { - foo: { - raw: false, - snippet: true, - snippetFallback: true, - snippetSize: 15, - }, - zoo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }, - isSnippetAllowed: () => true, - }; - - const actions = { - toggleRawForField: jest.fn(), - updateRawSizeForField: jest.fn(), - clearRawSizeForField: jest.fn(), - toggleSnippetForField: jest.fn(), - updateSnippetSizeForField: jest.fn(), - clearSnippetSizeForField: jest.fn(), - toggleSnippetFallbackForField: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - const getTableRows = (wrapper: ShallowWrapper) => wrapper.find(EuiTableRow); - const getBarTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(0); - const getFooTableRow = (wrapper: ShallowWrapper) => getTableRows(wrapper).at(1); - - it('renders a table row for each field, sorted by field name', () => { - const wrapper = shallow(); - const tableRows = getTableRows(wrapper); - - expect(tableRows.length).toBe(3); - expect( - tableRows.at(0).find('[data-test-subj="ResultSettingFieldName"]').render().text() - ).toEqual('bar'); - expect( - tableRows.at(1).find('[data-test-subj="ResultSettingFieldName"]').render().text() - ).toEqual('foo'); - expect( - tableRows.at(2).find('[data-test-subj="ResultSettingFieldName"]').render().text() - ).toEqual('zoo'); - }); - - describe('the "raw" checkbox within each table row', () => { - const getRawCheckbox = () => { - const wrapper = shallow(); - const tableRow = getBarTableRow(wrapper); - return tableRow.find('[data-test-subj="ResultSettingRawCheckBox"]'); - }; - - it('is rendered with its checked property set from state', () => { - const rawCheckbox = getRawCheckbox(); - expect(rawCheckbox.prop('checked')).toEqual(values.textResultFields.bar.raw); - }); - - it("calls 'toggleRawForField' when it is clicked by a user", () => { - const rawCheckbox = getRawCheckbox(); - rawCheckbox.simulate('change'); - expect(actions.toggleRawForField).toHaveBeenCalledWith('bar'); - }); - }); - - describe('the "snippet" checkbox within each table row', () => { - const getSnippetCheckbox = () => { - const wrapper = shallow(); - const tableRow = getFooTableRow(wrapper); - return tableRow.find('[data-test-subj="ResultSettingSnippetTextBox"]'); - }; - - it('is rendered with its checked property set from state', () => { - const snippetCheckbox = getSnippetCheckbox(); - expect(snippetCheckbox.prop('checked')).toEqual(values.textResultFields.foo.snippet); - }); - - it("calls 'toggleRawForField' when it is clicked by a user", () => { - const snippetCheckbox = getSnippetCheckbox(); - snippetCheckbox.simulate('change'); - expect(actions.toggleSnippetForField).toHaveBeenCalledWith('foo'); - }); - - describe('when "isSnippetAllowed" return false', () => { - values.isSnippetAllowed = () => false; - - it('the snippet checkbox is disabled', () => { - const snippetCheckbox = getSnippetCheckbox(); - expect(snippetCheckbox.prop('disabled')).toEqual(true); - }); - }); - }); - - describe('the "fallback" checkbox within each table row', () => { - const getFallbackCheckbox = () => { - const wrapper = shallow(); - const tableRow = getFooTableRow(wrapper); - return tableRow.find('[data-test-subj="ResultSettingFallbackTextBox"]'); - }; - - it('is rendered with its checked property set from state', () => { - const fallbackCheckbox = getFallbackCheckbox(); - expect(fallbackCheckbox.prop('checked')).toEqual(values.textResultFields.foo.snippetFallback); - }); - - it('is disabled if snippets are disabled for this field', () => { - const wrapper = shallow(); - const tableRow = getBarTableRow(wrapper); - const fallbackCheckbox = tableRow.find('[data-test-subj="ResultSettingFallbackTextBox"]'); - expect(fallbackCheckbox.prop('disabled')).toEqual(true); - }); - - it("calls 'toggleSnippetFallbackForField' when it is clicked by a user", () => { - const fallbackCheckbox = getFallbackCheckbox(); - fallbackCheckbox.simulate('change'); - expect(actions.toggleSnippetFallbackForField).toHaveBeenCalledWith('foo'); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx deleted file mode 100644 index 67e782886ae75..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_body.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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, { useMemo } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiTableRow, EuiTableRowCell, EuiTableRowCellCheckbox, EuiCheckbox } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { ResultSettingsLogic } from '../result_settings_logic'; -import { FieldResultSetting } from '../types'; - -import { FieldNumber } from './field_number'; - -export const TextFieldsBody: React.FC = () => { - const { isSnippetAllowed, textResultFields } = useValues(ResultSettingsLogic); - const { - toggleRawForField, - updateRawSizeForField, - clearRawSizeForField, - toggleSnippetForField, - updateSnippetSizeForField, - clearSnippetSizeForField, - toggleSnippetFallbackForField, - } = useActions(ResultSettingsLogic); - - const resultSettingsArray: Array<[string, Partial]> = useMemo(() => { - return Object.entries(textResultFields).sort(([aFieldName], [bFieldName]) => - aFieldName > bFieldName ? 1 : -1 - ); - }, [textResultFields]); - - return ( - <> - {resultSettingsArray.map(([fieldName, fieldSettings]) => ( - - - {fieldName} - - - { - toggleRawForField(fieldName); - }} - /> - - - - - - { - toggleSnippetForField(fieldName); - }} - /> - - - { - toggleSnippetFallbackForField(fieldName); - }} - /> - - - - - - ))} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx deleted file mode 100644 index be7d18e375fdd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { TextFieldsHeader } from './text_fields_header'; - -describe('TextFieldsHeader', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx deleted file mode 100644 index 2ec4e03011158..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_table/text_fields_header.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { EuiTableRow, EuiTableHeaderCell } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -export const TextFieldsHeader: React.FC = () => { - return ( - <> - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.textFieldsTitle', - { defaultMessage: 'Text fields' } - )} - - {/* TODO Right now the stacked "Raw" ths leads screen readers to reading out Raw - Raw - Raw 3x in a row once you get down to the non-text fields. We should consider either: - Channging this "Raw" column to something like "Enabled" - Or losing the RAW vs HIGHLIGHTING top-level headings */} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.rawTitle', - { defaultMessage: 'Raw' } - )} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle', - { defaultMessage: 'Max size' } - )} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.snippetTitle', - { defaultMessage: 'Snippet' } - )} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.fallbackTitle', - { defaultMessage: 'Fallback' } - )} - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.column.maxSizeTitle', - { defaultMessage: 'Max size' } - )} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/index.ts deleted file mode 100644 index 257ad27fe8748..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SampleResponse } from './sample_response'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.test.tsx deleted file mode 100644 index 964e466582d60..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCodeBlock, EuiFieldSearch } from '@elastic/eui'; - -import { SampleResponse } from './sample_response'; - -describe('SampleResponse', () => { - const actions = { - queryChanged: jest.fn(), - getSearchResults: jest.fn(), - }; - - const values = { - reducedServerResultFields: {}, - query: 'foo', - response: { - bar: 'baz', - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - setMockValues(values); - }); - - it('renders a text box with the current user "query" value from state', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('foo'); - }); - - it('updates the "query" value in state when a user updates the text in the text box', () => { - const wrapper = shallow(); - wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'bar' } }); - expect(actions.queryChanged).toHaveBeenCalledWith('bar'); - }); - - it('will call getSearchResults with the current value of query and reducedServerResultFields in a useEffect, which updates the displayed response', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('foo'); - }); - - it('renders the response from the given user "query" in a code block', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiCodeBlock).prop('children')).toEqual('{\n "bar": "baz"\n}'); - }); - - it('renders a plain old string in the code block if the response is a string', () => { - setMockValues({ - response: 'No results.', - }); - const wrapper = shallow(); - expect(wrapper.find(EuiCodeBlock).prop('children')).toEqual('No results.'); - }); - - it('will not render a code block at all if there is no response yet', () => { - setMockValues({ - response: null, - }); - const wrapper = shallow(); - expect(wrapper.find(EuiCodeBlock).exists()).toEqual(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx deleted file mode 100644 index 2d0cced3730ba..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiCodeBlock, - EuiFieldSearch, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { QueryPerformance } from '../query_performance'; -import { ResultSettingsLogic } from '../result_settings_logic'; - -import { SampleResponseLogic } from './sample_response_logic'; - -export const SampleResponse: React.FC = () => { - const { reducedServerResultFields } = useValues(ResultSettingsLogic); - - const { query, response } = useValues(SampleResponseLogic); - const { queryChanged, getSearchResults } = useActions(SampleResponseLogic); - - useEffect(() => { - getSearchResults(query, reducedServerResultFields); - }, [query, reducedServerResultFields]); - - return ( - - - - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponseTitle', - { defaultMessage: 'Sample response' } - )} -

    -
    -
    - - - -
    - - queryChanged(e.target.value)} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.inputPlaceholder', - { defaultMessage: 'Type a search query to test a response...' } - )} - data-test-subj="ResultSettingsQuerySampleResponse" - /> - - {!!response && ( - - {typeof response === 'string' ? response : JSON.stringify(response, null, 2)} - - )} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts deleted file mode 100644 index 317693f21bbda..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { flashAPIErrors } from '../../../../shared/flash_messages'; - -import { SampleResponseLogic } from './sample_response_logic'; - -describe('SampleResponseLogic', () => { - const { mount } = new LogicMounter(SampleResponseLogic); - const { http } = mockHttpValues; - - const DEFAULT_VALUES = { - query: '', - response: null, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(SampleResponseLogic.values).toEqual({ - ...DEFAULT_VALUES, - }); - }); - - describe('actions', () => { - describe('queryChanged', () => { - it('updates the query', () => { - mount({ - query: '', - }); - - SampleResponseLogic.actions.queryChanged('foo'); - - expect(SampleResponseLogic.values).toEqual({ - ...DEFAULT_VALUES, - query: 'foo', - }); - }); - }); - - describe('getSearchResultsSuccess', () => { - it('sets the response from a search API request', () => { - mount({ - response: null, - }); - - SampleResponseLogic.actions.getSearchResultsSuccess({}); - - expect(SampleResponseLogic.values).toEqual({ - ...DEFAULT_VALUES, - response: {}, - }); - }); - }); - - describe('getSearchResultsFailure', () => { - it('sets a string response from a search API request', () => { - mount({ - response: null, - }); - - SampleResponseLogic.actions.getSearchResultsFailure('An error occured.'); - - expect(SampleResponseLogic.values).toEqual({ - ...DEFAULT_VALUES, - response: 'An error occured.', - }); - }); - }); - }); - - describe('listeners', () => { - describe('getSearchResults', () => { - beforeAll(() => jest.useFakeTimers({ legacyFakeTimers: true })); - afterAll(() => jest.useRealTimers()); - - it('makes a search API request and calls getSearchResultsSuccess with the first result of the response', async () => { - mount(); - jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsSuccess'); - - http.post.mockReturnValue( - Promise.resolve({ - results: [ - { id: { raw: 'foo' }, _meta: {} }, - { id: { raw: 'bar' }, _meta: {} }, - { id: { raw: 'baz' }, _meta: {} }, - ], - }) - ); - - SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } }); - jest.runAllTimers(); - await nextTick(); - - expect(SampleResponseLogic.actions.getSearchResultsSuccess).toHaveBeenCalledWith({ - // Note that the _meta field was stripped from the result - id: { raw: 'foo' }, - }); - }); - - it('calls getSearchResultsSuccess with a "No Results." message if there are no results', async () => { - mount(); - jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsSuccess'); - - http.post.mockReturnValue( - Promise.resolve({ - results: [], - }) - ); - - SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } }); - jest.runAllTimers(); - await nextTick(); - - expect(SampleResponseLogic.actions.getSearchResultsSuccess).toHaveBeenCalledWith( - 'No results.' - ); - }); - - it('handles 500 errors by setting a generic error response and showing a flash message error', async () => { - mount(); - jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsFailure'); - - const error = { - response: { - status: 500, - }, - }; - - http.post.mockReturnValueOnce(Promise.reject(error)); - - SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } }); - jest.runAllTimers(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith(error); - expect(SampleResponseLogic.actions.getSearchResultsFailure).toHaveBeenCalledWith( - 'An error occured.' - ); - }); - - it('handles 400 errors by setting the response, but does not show a flash error message', async () => { - mount(); - jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsFailure'); - - http.post.mockReturnValueOnce( - Promise.reject({ - response: { - status: 400, - }, - body: { - attributes: { - errors: ['A validation error occurred.'], - }, - }, - }) - ); - - SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } }); - jest.runAllTimers(); - await nextTick(); - - expect(SampleResponseLogic.actions.getSearchResultsFailure).toHaveBeenCalledWith({ - errors: ['A validation error occurred.'], - }); - }); - - it('sets a generic message on a 400 error if no custom message is provided in the response', async () => { - mount(); - jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsFailure'); - - http.post.mockReturnValueOnce( - Promise.reject({ - response: { - status: 400, - }, - }) - ); - - SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } }); - jest.runAllTimers(); - await nextTick(); - - expect(SampleResponseLogic.actions.getSearchResultsFailure).toHaveBeenCalledWith( - 'An error occured.' - ); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts deleted file mode 100644 index ce920c172b8ab..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { flashAPIErrors } from '../../../../shared/flash_messages'; - -import { HttpLogic } from '../../../../shared/http'; -import { EngineLogic } from '../../engine'; - -import { FieldValue } from '../../result/types'; -import { SampleSearchResponse, ServerFieldResultSettingObject } from '../types'; - -const NO_RESULTS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.noResultsMessage', - { defaultMessage: 'No results.' } -); - -const ERROR_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.errorMessage', - { defaultMessage: 'An error occured.' } -); - -interface SampleResponseValues { - query: string; - response: SampleSearchResponse | string | null; -} - -interface SampleResponseActions { - queryChanged: (query: string) => { query: string }; - getSearchResultsSuccess: (response: SampleSearchResponse | string) => { - response: SampleSearchResponse | string; - }; - getSearchResultsFailure: (response: string) => { response: string }; - getSearchResults: ( - query: string, - resultFields: ServerFieldResultSettingObject - ) => { query: string; resultFields: ServerFieldResultSettingObject }; -} - -export const SampleResponseLogic = kea>({ - path: ['enterprise_search', 'app_search', 'sample_response_logic'], - actions: { - queryChanged: (query) => ({ query }), - getSearchResultsSuccess: (response) => ({ response }), - getSearchResultsFailure: (response) => ({ response }), - getSearchResults: (query, resultFields) => ({ query, resultFields }), - }, - reducers: { - // @ts-expect-error upgrade typescript v5.1.6 - query: ['', { queryChanged: (_, { query }) => query }], - response: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - getSearchResultsSuccess: (_, { response }) => response, - // @ts-expect-error upgrade typescript v5.1.6 - getSearchResultsFailure: (_, { response }) => response, - }, - ], - }, - listeners: ({ actions }) => ({ - getSearchResults: async ({ query, resultFields }, breakpoint) => { - await breakpoint(250); - - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - const url = `/internal/app_search/engines/${engineName}/search`; - - try { - const response = await http.post<{ results: Array> }>(url, { - query: { query }, - body: JSON.stringify({ - page: { - size: 1, - current: 1, - }, - result_fields: resultFields, - }), - }); - - const result = response.results?.[0]; - actions.getSearchResultsSuccess( - // @ts-expect-error TS2345 - result ? { ...result, _meta: undefined } : NO_RESULTS_MESSAGE - ); - } catch (e) { - if (e.response.status >= 500) { - // 4XX Validation errors are expected, as a user could enter something like 2 as a size, which is out of valid range. - // In this case, we simply render the message from the server as the response. - // - // 5xx Server errors are unexpected, and need to be reported in a flash message. - flashAPIErrors(e); - actions.getSearchResultsFailure(ERROR_MESSAGE); - } else { - actions.getSearchResultsFailure(e.body?.attributes || ERROR_MESSAGE); - } - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts deleted file mode 100644 index 1174f65523d99..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { FieldValue } from '../result/types'; - -export interface ServerFieldResultSetting { - raw?: - | { - size?: number; - } - | boolean; - snippet?: - | { - size?: number; - fallback?: boolean; - } - | boolean; -} - -export type ServerFieldResultSettingObject = Record; - -export interface FieldResultSetting { - raw: boolean; - rawSize?: number; - snippet: boolean; - snippetSize?: number; - snippetFallback: boolean; -} - -export type FieldResultSettingObject = Record; - -export type SampleSearchResponse = Record; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts deleted file mode 100644 index 38b617b8f119e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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 { SchemaType } from '../../../shared/schema/types'; - -import { - areFieldsAtDefaultSettings, - convertServerResultFieldsToResultFields, - convertToServerFieldResultSetting, - clearAllFields, - resetAllFields, - splitResultFields, - areFieldsEmpty, -} from './utils'; - -describe('clearAllFields', () => { - it('will reset every key in an object back to an empty object', () => { - expect( - clearAllFields({ - foo: { raw: false, snippet: false, snippetFallback: false }, - bar: { raw: true, snippet: false, snippetFallback: true }, - }) - ).toEqual({ - foo: {}, - bar: {}, - }); - }); -}); - -describe('resetAllFields', () => { - it('will reset every key in an object back to a default object', () => { - expect( - resetAllFields({ - foo: { raw: false, snippet: true, snippetFallback: true }, - bar: { raw: false, snippet: true, snippetFallback: true }, - }) - ).toEqual({ - foo: { raw: true, snippet: false, snippetFallback: false }, - bar: { raw: true, snippet: false, snippetFallback: false }, - }); - }); -}); - -describe('convertServerResultFieldsToResultFields', () => { - it('will convert a server settings object to a format that the front-end expects', () => { - expect( - convertServerResultFieldsToResultFields( - { - foo: { - raw: { size: 5 }, - snippet: { size: 3, fallback: true }, - }, - }, - { - foo: { type: SchemaType.Text, capabilities: {} }, - } - ) - ).toEqual({ - foo: { - raw: true, - rawSize: 5, - snippet: true, - snippetFallback: true, - snippetSize: 3, - }, - }); - }); -}); - -describe('convertToServerFieldResultSetting', () => { - it('will convert a settings object to a format that the server expects', () => { - expect( - convertToServerFieldResultSetting({ - raw: true, - rawSize: 5, - snippet: true, - snippetFallback: true, - snippetSize: 3, - }) - ).toEqual({ - raw: { size: 5 }, - snippet: { size: 3, fallback: true }, - }); - }); - - it('will not include snippet or raw information if they are set to false', () => { - expect( - convertToServerFieldResultSetting({ - raw: false, - rawSize: 5, - snippet: false, - snippetFallback: true, - snippetSize: 3, - }) - ).toEqual({}); - }); - - it('will not include sizes if they are not included, or fallback if it is false', () => { - expect( - convertToServerFieldResultSetting({ - raw: true, - snippet: true, - snippetFallback: false, - }) - ).toEqual({ - raw: {}, - snippet: {}, - }); - }); -}); - -describe('splitResultFields', () => { - it('will split results based on their schema type', () => { - expect( - splitResultFields( - { - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }, - { - foo: { type: SchemaType.Text, capabilities: {} }, - bar: { type: SchemaType.Number, capabilities: {} }, - nested_object: { type: SchemaType.Nested, capabilities: {} }, - } - ) - ).toEqual({ - nonTextResultFields: { - bar: { raw: true, rawSize: 5, snippet: false, snippetFallback: false }, - }, - textResultFields: { foo: { raw: true, rawSize: 5, snippet: false, snippetFallback: false } }, - }); - }); -}); - -describe('areFieldsEmpty', () => { - it('should return true if all fields are empty or have all properties disabled', () => { - expect( - areFieldsEmpty({ - foo: {}, - bar: { raw: false, snippet: false }, - baz: { raw: false }, - }) - ).toBe(true); - }); - - it('should return false otherwise', () => { - expect( - areFieldsEmpty({ - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }) - ).toBe(false); - }); -}); - -describe('areFieldsAtDefaultSettings', () => { - it('will return true if all settings for all fields are at their defaults', () => { - expect( - areFieldsAtDefaultSettings({ - foo: { - raw: true, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - snippet: false, - snippetFallback: false, - }, - }) - ).toEqual(true); - }); - - it('will return false otherwise', () => { - expect( - areFieldsAtDefaultSettings({ - foo: { - raw: true, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: false, - snippet: true, - snippetFallback: true, - }, - }) - ).toEqual(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts deleted file mode 100644 index 60d12c0b5889f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 { isEqual } from 'lodash'; - -import { AdvancedSchema, SchemaType } from '../../../shared/schema/types'; - -import { DEFAULT_FIELD_SETTINGS, DISABLED_FIELD_SETTINGS } from './constants'; -import { - FieldResultSetting, - FieldResultSettingObject, - ServerFieldResultSetting, - ServerFieldResultSettingObject, -} from './types'; - -const updateAllFields = ( - fields: FieldResultSettingObject | ServerFieldResultSettingObject, - newValue: FieldResultSetting | {} -) => { - return Object.keys(fields).reduce( - (acc, fieldName) => ({ ...acc, [fieldName]: { ...newValue } }), - {} - ); -}; - -const convertToFieldResultSetting = (serverFieldResultSetting: ServerFieldResultSetting) => { - const fieldResultSetting: FieldResultSetting = { - raw: !!serverFieldResultSetting.raw, - snippet: !!serverFieldResultSetting.snippet, - snippetFallback: !!( - serverFieldResultSetting.snippet && - typeof serverFieldResultSetting.snippet === 'object' && - serverFieldResultSetting.snippet.fallback - ), - }; - - if ( - serverFieldResultSetting.raw && - typeof serverFieldResultSetting.raw === 'object' && - serverFieldResultSetting.raw.size - ) { - fieldResultSetting.rawSize = serverFieldResultSetting.raw.size; - } - - if ( - serverFieldResultSetting.snippet && - typeof serverFieldResultSetting.snippet === 'object' && - serverFieldResultSetting.snippet.size - ) { - fieldResultSetting.snippetSize = serverFieldResultSetting.snippet.size; - } - - return fieldResultSetting; -}; - -export const clearAllFields = (fields: FieldResultSettingObject) => updateAllFields(fields, {}); - -export const resetAllFields = (fields: FieldResultSettingObject) => - updateAllFields(fields, DEFAULT_FIELD_SETTINGS); - -export const convertServerResultFieldsToResultFields = ( - serverResultFields: ServerFieldResultSettingObject, - schema: AdvancedSchema -) => { - const resultFields: FieldResultSettingObject = Object.keys(schema).reduce( - (acc: FieldResultSettingObject, fieldName: string) => ({ - ...acc, - [fieldName]: serverResultFields[fieldName] - ? convertToFieldResultSetting(serverResultFields[fieldName]) - : DISABLED_FIELD_SETTINGS, - }), - {} - ); - return resultFields; -}; - -export const convertToServerFieldResultSetting = (fieldResultSetting: FieldResultSetting) => { - const serverFieldResultSetting: ServerFieldResultSetting = {}; - if (fieldResultSetting.raw) { - serverFieldResultSetting.raw = {}; - if (fieldResultSetting.rawSize) { - serverFieldResultSetting.raw.size = fieldResultSetting.rawSize; - } - } - - if (fieldResultSetting.snippet) { - serverFieldResultSetting.snippet = {}; - if (fieldResultSetting.snippetFallback) { - serverFieldResultSetting.snippet.fallback = fieldResultSetting.snippetFallback; - } - if (fieldResultSetting.snippetSize) { - serverFieldResultSetting.snippet.size = fieldResultSetting.snippetSize; - } - } - - return serverFieldResultSetting; -}; - -export const splitResultFields = (resultFields: FieldResultSettingObject, schema: AdvancedSchema) => - Object.entries(resultFields).reduce( - (acc, [fieldName, resultFieldSettings]) => { - const fieldType = schema[fieldName].type; - const targetField = - fieldType === SchemaType.Text ? 'textResultFields' : 'nonTextResultFields'; - return { ...acc, [targetField]: { ...acc[targetField], [fieldName]: resultFieldSettings } }; - }, - { textResultFields: {}, nonTextResultFields: {} } - ); - -export const areFieldsEmpty = (fields: FieldResultSettingObject) => { - const anyNonEmptyField = Object.values(fields).find((field) => { - return (field as FieldResultSetting).raw || (field as FieldResultSetting).snippet; - }); - return !anyNonEmptyField; -}; - -export const areFieldsAtDefaultSettings = (fields: FieldResultSettingObject) => { - const anyNonDefaultSettingsValue = Object.values(fields).find((resultSettings) => { - return !isEqual(resultSettings, DEFAULT_FIELD_SETTINGS); - }); - return !anyNonDefaultSettingsValue; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts deleted file mode 100644 index da7b2335373bf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { AdvanceRoleType } from '../../types'; - -export const ROLE_MAPPING_DELETED_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMappingDeletedMessage', - { - defaultMessage: 'Your role mapping was deleted', - } -); - -export const ROLE_MAPPING_CREATED_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMappingCreatedMessage', - { - defaultMessage: 'Your role mapping was created', - } -); - -export const ROLE_MAPPING_UPDATED_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMappingUpdatedMessage', - { - defaultMessage: 'Your role mapping was updated', - } -); - -export const ROLE_MAPPINGS_ENGINE_ACCESS_HEADING = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMappingsEngineAccessHeading', - { - defaultMessage: 'Engine access', - } -); - -export const DEV_ROLE_TYPE_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.DEV_ROLE_TYPE_DESCRIPTION', - { - defaultMessage: 'Devs can manage all aspects of an engine.', - } -); - -export const EDITOR_ROLE_TYPE_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.editorRoleTypeDescription', - { - defaultMessage: 'Editors can manage search settings.', - } -); - -export const ANALYST_ROLE_TYPE_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.analystRoleTypeDescription', - { - defaultMessage: 'Analysts can only view documents, query tester, and analytics.', - } -); - -export const OWNER_ROLE_TYPE_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.ownerRoleTypeDescription', - { - defaultMessage: - 'Owners can do anything. There can be many owners on the account, but there must be at least one owner at any time.', - } -); - -export const ADMIN_ROLE_TYPE_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.adminRoleTypeDescription', - { - defaultMessage: 'Admins can do anything, except manage account settings.', - } -); - -export const ENGINE_REQUIRED_ERROR = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineRequiredError', - { - defaultMessage: 'At least one assigned engine is required.', - } -); - -export const ALL_ENGINES_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.allEnginesLabel', - { - defaultMessage: 'Assign to all engines', - } -); - -export const ALL_ENGINES_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.allEnginesDescription', - { - defaultMessage: - 'Assigning to all engines includes all current and future engines as created and administered at a later date.', - } -); - -export const SPECIFIC_ENGINES_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.specificEnginesLabel', - { - defaultMessage: 'Assign to specific engines', - } -); - -export const SPECIFIC_ENGINES_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.specificEnginesDescription', - { - defaultMessage: 'Assign to a select set of engines statically.', - } -); - -export const ENGINE_ASSIGNMENT_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engineAssignmentLabel', - { - defaultMessage: 'Engine assignment', - } -); - -export const ADVANCED_ROLE_TYPES = [ - { - id: 'dev', - description: DEV_ROLE_TYPE_DESCRIPTION, - }, - { - id: 'editor', - description: EDITOR_ROLE_TYPE_DESCRIPTION, - }, - { - id: 'analyst', - description: ANALYST_ROLE_TYPE_DESCRIPTION, - }, -] as AdvanceRoleType[]; - -export const STANDARD_ROLE_TYPES = [ - { - id: 'owner', - description: OWNER_ROLE_TYPE_DESCRIPTION, - }, - { - id: 'admin', - description: ADMIN_ROLE_TYPE_DESCRIPTION, - }, -] as AdvanceRoleType[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx deleted file mode 100644 index 74ca02e1d91db..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { DEFAULT_INITIAL_APP_DATA } from '../../../../../common/__mocks__'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import { engines } from '../../__mocks__/engines.mock'; - -import React from 'react'; - -import { waitFor } from '@testing-library/react'; -import { shallow } from 'enzyme'; - -import { EuiComboBox, EuiComboBoxOptionOption, EuiRadioGroup } from '@elastic/eui'; - -import { asRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; - -import { EngineAssignmentSelector } from './engine_assignment_selector'; - -describe('EngineAssignmentSelector', () => { - const mockRole = DEFAULT_INITIAL_APP_DATA.appSearch.role; - const actions = { - initializeRoleMappings: jest.fn(), - initializeRoleMapping: jest.fn(), - handleSaveMapping: jest.fn(), - handleEngineSelectionChange: jest.fn(), - handleAccessAllEnginesChange: jest.fn(), - handleAttributeValueChange: jest.fn(), - handleAttributeSelectorChange: jest.fn(), - handleDeleteMapping: jest.fn(), - handleRoleChange: jest.fn(), - resetState: jest.fn(), - }; - - const mockValues = { - attributes: [], - elasticsearchRoles: [], - hasAdvancedRoles: true, - dataLoading: false, - roleType: 'admin', - roleMappings: [asRoleMapping], - attributeValue: '', - attributeName: 'username', - availableEngines: engines, - selectedEngines: new Set(), - accessAllEngines: false, - myRole: { - availableRoleTypes: mockRole.ability.availableRoleTypes, - }, - roleMappingErrors: [], - }; - - beforeEach(() => { - setMockActions(actions); - setMockValues(mockValues); - }); - - it('renders', () => { - setMockValues({ ...mockValues, roleMapping: asRoleMapping }); - const wrapper = shallow(); - - expect(wrapper.find(EuiRadioGroup)).toHaveLength(1); - expect(wrapper.find(EuiComboBox)).toHaveLength(1); - }); - - it('sets initial selected state when accessAllEngines is true', () => { - setMockValues({ ...mockValues, accessAllEngines: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiRadioGroup).prop('idSelected')).toBe('all'); - }); - - it('handles all/specific engines radio change', () => { - const wrapper = shallow(); - const radio = wrapper.find(EuiRadioGroup); - radio.simulate('change', { target: { checked: false } }); - - expect(actions.handleAccessAllEnginesChange).toHaveBeenCalledWith(false); - }); - - it('handles engine checkbox click', async () => { - const wrapper = shallow(); - await waitFor(() => - ( - wrapper.find(EuiComboBox).props() as unknown as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - } - ).onChange([{ label: engines[0].name, value: engines[0].name }]) - ); - wrapper.update(); - - expect(actions.handleEngineSelectionChange).toHaveBeenCalledWith([engines[0].name]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.tsx deleted file mode 100644 index 09ee0940cee9d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 { EuiComboBox, EuiFormRow, EuiHorizontalRule, EuiRadioGroup } from '@elastic/eui'; - -import { RoleOptionLabel } from '../../../shared/role_mapping'; - -import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines'; - -import { - ENGINE_REQUIRED_ERROR, - ALL_ENGINES_LABEL, - ALL_ENGINES_DESCRIPTION, - SPECIFIC_ENGINES_LABEL, - SPECIFIC_ENGINES_DESCRIPTION, - ENGINE_ASSIGNMENT_LABEL, -} from './constants'; -import { RoleMappingsLogic } from './role_mappings_logic'; - -export const EngineAssignmentSelector: React.FC = () => { - const { handleAccessAllEnginesChange, handleEngineSelectionChange } = - useActions(RoleMappingsLogic); - - const { accessAllEngines, availableEngines, roleType, selectedEngines, selectedOptions } = - useValues(RoleMappingsLogic); - - const hasEngineAssignment = selectedEngines.size > 0 || accessAllEngines; - - const engineOptions = [ - { - id: 'all', - label: , - }, - { - id: 'specific', - label: ( - - ), - }, - ]; - - return ( - <> - - - handleAccessAllEnginesChange(id === 'all')} - legend={{ - children: {ENGINE_ASSIGNMENT_LABEL}, - }} - /> - - - ({ label: name, value: name }))} - onChange={(options) => { - handleEngineSelectionChange(options.map(({ value }) => value as string)); - }} - fullWidth - isDisabled={accessAllEngines || !roleHasScopedEngines(roleType)} - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts deleted file mode 100644 index 19062cf44c17a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { RoleMappings } from './role_mappings'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx deleted file mode 100644 index d81009e58f7d1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { DEFAULT_INITIAL_APP_DATA } from '../../../../../common/__mocks__'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import { engines } from '../../__mocks__/engines.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { AttributeSelector, RoleSelector, RoleMappingFlyout } from '../../../shared/role_mapping'; -import { asRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; - -import { STANDARD_ROLE_TYPES } from './constants'; - -import { EngineAssignmentSelector } from './engine_assignment_selector'; -import { RoleMapping } from './role_mapping'; - -describe('RoleMapping', () => { - const mockRole = DEFAULT_INITIAL_APP_DATA.appSearch.role; - const actions = { - initializeRoleMappings: jest.fn(), - initializeRoleMapping: jest.fn(), - handleSaveMapping: jest.fn(), - handleEngineSelectionChange: jest.fn(), - handleAccessAllEnginesChange: jest.fn(), - handleAttributeValueChange: jest.fn(), - handleAttributeSelectorChange: jest.fn(), - handleDeleteMapping: jest.fn(), - handleRoleChange: jest.fn(), - resetState: jest.fn(), - }; - - const mockValues = { - attributes: [], - elasticsearchRoles: [], - hasAdvancedRoles: true, - dataLoading: false, - roleType: 'admin', - roleMappings: [asRoleMapping], - attributeValue: '', - attributeName: 'username', - availableEngines: engines, - selectedEngines: new Set(), - accessAllEngines: false, - myRole: { - availableRoleTypes: mockRole.ability.availableRoleTypes, - }, - roleMappingErrors: [], - }; - - beforeEach(() => { - setMockActions(actions); - setMockValues(mockValues); - }); - - it('renders', () => { - setMockValues({ ...mockValues, roleMapping: asRoleMapping }); - const wrapper = shallow(); - - expect(wrapper.find(AttributeSelector)).toHaveLength(1); - expect(wrapper.find(RoleSelector)).toHaveLength(1); - expect(wrapper.find(EngineAssignmentSelector)).toHaveLength(1); - }); - - it('only passes standard role options for non-advanced roles', () => { - setMockValues({ ...mockValues, hasAdvancedRoles: false }); - const wrapper = shallow(); - - expect(wrapper.find(RoleSelector).prop('roleOptions')).toHaveLength(STANDARD_ROLE_TYPES.length); - }); - - it('enables flyout when attribute value is valid', () => { - setMockValues({ - ...mockValues, - attributeValue: 'foo', - attributeName: 'role', - accessAllEngines: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(RoleMappingFlyout).prop('disabled')).toBe(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx deleted file mode 100644 index a9fb67198cb79..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { EuiForm, EuiSpacer } from '@elastic/eui'; - -import { AttributeSelector, RoleSelector, RoleMappingFlyout } from '../../../shared/role_mapping'; -import { AppLogic } from '../../app_logic'; -import { AdvanceRoleType } from '../../types'; - -import { ADVANCED_ROLE_TYPES, STANDARD_ROLE_TYPES } from './constants'; -import { EngineAssignmentSelector } from './engine_assignment_selector'; -import { RoleMappingsLogic } from './role_mappings_logic'; - -export const RoleMapping: React.FC = () => { - const { myRole } = useValues(AppLogic); - - const { - handleAttributeSelectorChange, - handleAttributeValueChange, - handleRoleChange, - handleSaveMapping, - closeUsersAndRolesFlyout, - } = useActions(RoleMappingsLogic); - - const { - accessAllEngines, - attributeName, - attributeValue, - attributes, - elasticsearchRoles, - hasAdvancedRoles, - roleMapping, - roleType, - selectedEngines, - roleMappingErrors, - formLoading, - } = useValues(RoleMappingsLogic); - - const isNew = !roleMapping; - const hasEngineAssignment = selectedEngines.size > 0 || accessAllEngines; - const attributeValueInvalid = attributeName !== 'role' && !attributeValue; - - const mapRoleOptions = ({ id, description }: AdvanceRoleType) => ({ - id, - description, - disabled: !myRole.availableRoleTypes.includes(id), - }); - - const standardRoleOptions = STANDARD_ROLE_TYPES.map(mapRoleOptions); - const advancedRoleOptions = ADVANCED_ROLE_TYPES.map(mapRoleOptions); - - const roleOptions = hasAdvancedRoles - ? [...standardRoleOptions, ...advancedRoleOptions] - : standardRoleOptions; - - return ( - - 0} error={roleMappingErrors}> - - - - {hasAdvancedRoles && } - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx deleted file mode 100644 index af453975278a6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { - RoleMappingsTable, - RoleMappingsHeading, - UsersHeading, - UsersEmptyPrompt, -} from '../../../shared/role_mapping'; -import { - asRoleMapping, - asSingleUserRoleMapping, -} from '../../../shared/role_mapping/__mocks__/roles'; - -import { RoleMapping } from './role_mapping'; -import { RoleMappings } from './role_mappings'; -import { User } from './user'; - -describe('RoleMappings', () => { - const initializeRoleMappings = jest.fn(); - const initializeRoleMapping = jest.fn(); - const initializeSingleUserRoleMapping = jest.fn(); - const handleDeleteMapping = jest.fn(); - const mockValues = { - roleMappings: [asRoleMapping], - dataLoading: false, - singleUserRoleMappings: [asSingleUserRoleMapping], - singleUserRoleMappingFlyoutOpen: false, - }; - - beforeEach(() => { - setMockActions({ - initializeRoleMappings, - initializeRoleMapping, - initializeSingleUserRoleMapping, - handleDeleteMapping, - }); - setMockValues(mockValues); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(RoleMappingsTable)).toHaveLength(1); - }); - - it('renders RoleMapping flyout', () => { - setMockValues({ ...mockValues, roleMappingFlyoutOpen: true }); - const wrapper = shallow(); - - expect(wrapper.find(RoleMapping)).toHaveLength(1); - }); - - it('renders User flyout', () => { - setMockValues({ ...mockValues, singleUserRoleMappingFlyoutOpen: true }); - const wrapper = shallow(); - - expect(wrapper.find(User)).toHaveLength(1); - }); - - it('handles RoleMappingsHeading onClick', () => { - const wrapper = shallow(); - wrapper.find(RoleMappingsHeading).prop('onClick')(); - - expect(initializeRoleMapping).toHaveBeenCalled(); - }); - - it('handles UsersHeading onClick', () => { - const wrapper = shallow(); - wrapper.find(UsersHeading).prop('onClick')(); - - expect(initializeSingleUserRoleMapping).toHaveBeenCalled(); - }); - - it('handles empty users state', () => { - setMockValues({ ...mockValues, singleUserRoleMappings: [] }); - const wrapper = shallow(); - - expect(wrapper.find(UsersEmptyPrompt)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx deleted file mode 100644 index 2ffe6cb357e54..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiSpacer } from '@elastic/eui'; - -import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { docLinks } from '../../../shared/doc_links'; -import { - RoleMappingsTable, - RoleMappingsHeading, - RolesEmptyPrompt, - UsersTable, - UsersHeading, - UsersEmptyPrompt, -} from '../../../shared/role_mapping'; -import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; - -import { AppSearchPageTemplate } from '../layout'; - -import { ROLE_MAPPINGS_ENGINE_ACCESS_HEADING } from './constants'; -import { RoleMapping } from './role_mapping'; -import { RoleMappingsLogic } from './role_mappings_logic'; -import { User } from './user'; - -export const RoleMappings: React.FC = () => { - const { - enableRoleBasedAccess, - initializeRoleMappings, - initializeRoleMapping, - initializeSingleUserRoleMapping, - handleDeleteMapping, - resetState, - } = useActions(RoleMappingsLogic); - const { - roleMappings, - singleUserRoleMappings, - dataLoading, - roleMappingFlyoutOpen, - singleUserRoleMappingFlyoutOpen, - } = useValues(RoleMappingsLogic); - - useEffect(() => { - initializeRoleMappings(); - return resetState; - }, []); - - const hasUsers = singleUserRoleMappings.length > 0; - - const rolesEmptyState = ( - - ); - - const roleMappingsSection = ( -
    - initializeRoleMapping()} - /> - -
    - ); - - const usersTable = ( - - ); - - const usersSection = ( - <> - initializeSingleUserRoleMapping()} /> - - {hasUsers ? usersTable : } - - ); - - return ( - - {roleMappingFlyoutOpen && } - {singleUserRoleMappingFlyoutOpen && } - {roleMappingsSection} - - {usersSection} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts deleted file mode 100644 index e78c1805c6ee7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts +++ /dev/null @@ -1,679 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; - -import { engines } from '../../__mocks__/engines.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; - -import { - asRoleMapping, - asSingleUserRoleMapping, -} from '../../../shared/role_mapping/__mocks__/roles'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { RoleMappingsLogic } from './role_mappings_logic'; - -const emptyUser = { username: '', email: '' }; - -describe('RoleMappingsLogic', () => { - const { http } = mockHttpValues; - const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; - const { mount } = new LogicMounter(RoleMappingsLogic); - const DEFAULT_VALUES = { - attributes: [], - elasticsearchRoles: [], - elasticsearchUser: emptyUser, - elasticsearchUsers: [], - roleMapping: null, - roleMappingFlyoutOpen: false, - roleMappings: [], - roleType: 'owner', - attributeValue: '', - attributeName: 'username', - dataLoading: true, - hasAdvancedRoles: false, - availableEngines: [], - selectedEngines: new Set(), - accessAllEngines: true, - selectedOptions: [], - roleMappingErrors: [], - singleUserRoleMapping: null, - singleUserRoleMappings: [], - singleUserRoleMappingFlyoutOpen: false, - userCreated: false, - userFormIsNewUser: true, - userFormUserIsExisting: true, - smtpSettingsPresent: false, - formLoading: false, - }; - - const mappingsServerProps = { - roleMappings: [asRoleMapping], - attributes: ['email', 'metadata', 'username', 'role'], - availableEngines: engines, - elasticsearchRoles: [], - hasAdvancedRoles: false, - singleUserRoleMappings: [asSingleUserRoleMapping], - elasticsearchUsers, - smtpSettingsPresent: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(RoleMappingsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('setRoleMappingsData', () => { - it('sets data based on server response from the `mappings` (plural) endpoint', () => { - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - roleMappings: [asRoleMapping], - dataLoading: false, - attributes: mappingsServerProps.attributes, - availableEngines: mappingsServerProps.availableEngines, - accessAllEngines: true, - attributeName: 'username', - attributeValue: '', - elasticsearchRoles: mappingsServerProps.elasticsearchRoles, - selectedEngines: new Set(), - selectedOptions: [], - elasticsearchUsers, - elasticsearchUser: elasticsearchUsers[0], - singleUserRoleMappings: [asSingleUserRoleMapping], - }); - }); - - it('handles fallback if no elasticsearch users present', () => { - RoleMappingsLogic.actions.setRoleMappingsData({ - ...mappingsServerProps, - elasticsearchUsers: [], - }); - - expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(emptyUser); - }); - }); - - describe('setElasticsearchUser', () => { - it('sets user', () => { - RoleMappingsLogic.actions.setElasticsearchUser(elasticsearchUsers[0]); - - expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(elasticsearchUsers[0]); - }); - - it('handles fallback if no user present', () => { - RoleMappingsLogic.actions.setElasticsearchUser(undefined); - - expect(RoleMappingsLogic.values.elasticsearchUser).toEqual(emptyUser); - }); - }); - - it('setSingleUserRoleMapping', () => { - RoleMappingsLogic.actions.setSingleUserRoleMapping(asSingleUserRoleMapping); - - expect(RoleMappingsLogic.values.singleUserRoleMapping).toEqual(asSingleUserRoleMapping); - }); - - it('handleRoleChange', () => { - RoleMappingsLogic.actions.handleRoleChange('dev'); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - roleType: 'dev', - accessAllEngines: false, - }); - }); - - describe('handleEngineSelectionChange', () => { - const engine = engines[0]; - const otherEngine = engines[1]; - const mountedValues = { - ...mappingsServerProps, - roleMappings: [ - { - ...asRoleMapping, - engines: [engine, otherEngine], - }, - ], - selectedEngines: new Set([engine.name]), - }; - - beforeEach(() => { - mount(mountedValues); - }); - - it('handles adding an engine to selected engines', () => { - RoleMappingsLogic.actions.handleEngineSelectionChange([engine.name, otherEngine.name]); - - expect(RoleMappingsLogic.values.selectedEngines).toEqual( - new Set([engine.name, otherEngine.name]) - ); - expect(RoleMappingsLogic.values.selectedOptions).toEqual([ - { label: engine.name, value: engine.name }, - { label: otherEngine.name, value: otherEngine.name }, - ]); - }); - it('handles removing an engine from selected engines', () => { - RoleMappingsLogic.actions.handleEngineSelectionChange([engine.name]); - - expect(RoleMappingsLogic.values.selectedEngines).toEqual(new Set([engine.name])); - expect(RoleMappingsLogic.values.selectedOptions).toEqual([ - { label: engine.name, value: engine.name }, - ]); - }); - }); - - it('handleAccessAllEnginesChange', () => { - RoleMappingsLogic.actions.handleAccessAllEnginesChange(false); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - accessAllEngines: false, - }); - }); - - it('setUserExistingRadioValue', () => { - RoleMappingsLogic.actions.setUserExistingRadioValue(false); - - expect(RoleMappingsLogic.values.userFormUserIsExisting).toEqual(false); - }); - - describe('handleAttributeSelectorChange', () => { - const elasticsearchRoles = ['foo', 'bar']; - - it('sets values correctly', () => { - mount({ - ...mappingsServerProps, - elasticsearchRoles, - }); - RoleMappingsLogic.actions.handleAttributeSelectorChange('role', elasticsearchRoles[0]); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - attributeValue: elasticsearchRoles[0], - roleMappings: [asRoleMapping], - roleMapping: null, - attributes: mappingsServerProps.attributes, - availableEngines: mappingsServerProps.availableEngines, - accessAllEngines: true, - attributeName: 'role', - elasticsearchRoles, - selectedEngines: new Set(), - elasticsearchUsers, - singleUserRoleMappings: [asSingleUserRoleMapping], - }); - }); - - it('correctly handles "role" fallback', () => { - RoleMappingsLogic.actions.handleAttributeSelectorChange('username', elasticsearchRoles[0]); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - attributeValue: '', - }); - }); - }); - - it('handleAttributeValueChange', () => { - RoleMappingsLogic.actions.handleAttributeValueChange('changed_value'); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - attributeValue: 'changed_value', - }); - }); - - it('resetState', () => { - mount(mappingsServerProps); - RoleMappingsLogic.actions.resetState(); - - expect(RoleMappingsLogic.values).toEqual(DEFAULT_VALUES); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('openRoleMappingFlyout', () => { - mount(mappingsServerProps); - RoleMappingsLogic.actions.openRoleMappingFlyout(); - - expect(RoleMappingsLogic.values.roleMappingFlyoutOpen).toEqual(true); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('openSingleUserRoleMappingFlyout', () => { - mount(mappingsServerProps); - RoleMappingsLogic.actions.openSingleUserRoleMappingFlyout(); - - expect(RoleMappingsLogic.values.singleUserRoleMappingFlyoutOpen).toEqual(true); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('closeUsersAndRolesFlyout', () => { - mount({ - ...mappingsServerProps, - roleMappingFlyoutOpen: true, - }); - RoleMappingsLogic.actions.closeUsersAndRolesFlyout(); - - expect(RoleMappingsLogic.values.roleMappingFlyoutOpen).toEqual(false); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('setElasticsearchUsernameValue', () => { - const username = 'newName'; - RoleMappingsLogic.actions.setElasticsearchUsernameValue(username); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - elasticsearchUser: { - ...RoleMappingsLogic.values.elasticsearchUser, - username, - }, - }); - }); - - it('setElasticsearchEmailValue', () => { - const email = 'newEmail@foo.cats'; - RoleMappingsLogic.actions.setElasticsearchEmailValue(email); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - elasticsearchUser: { - ...RoleMappingsLogic.values.elasticsearchUser, - email, - }, - }); - }); - - it('setUserCreated', () => { - RoleMappingsLogic.actions.setUserCreated(); - - expect(RoleMappingsLogic.values).toEqual({ - ...DEFAULT_VALUES, - userCreated: true, - }); - }); - }); - - describe('listeners', () => { - describe('enableRoleBasedAccess', () => { - it('calls API and sets values', async () => { - const initializeRoleMappingsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'initializeRoleMappings' - ); - http.post.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.enableRoleBasedAccess(); - - expect(RoleMappingsLogic.values.dataLoading).toEqual(true); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/role_mappings/enable_role_based_access' - ); - await nextTick(); - expect(initializeRoleMappingsSpy).toHaveBeenCalled(); - }); - - itShowsServerErrorAsFlashMessage(http.post, () => { - RoleMappingsLogic.actions.enableRoleBasedAccess(); - }); - }); - - describe('initializeRoleMappings', () => { - it('calls API and sets values', async () => { - const setRoleMappingsDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMappingsData'); - http.get.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.initializeRoleMappings(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/role_mappings'); - await nextTick(); - expect(setRoleMappingsDataSpy).toHaveBeenCalledWith(mappingsServerProps); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - RoleMappingsLogic.actions.initializeRoleMappings(); - }); - - it('resets roleMapping state', () => { - mount({ - ...mappingsServerProps, - roleMapping: asRoleMapping, - }); - RoleMappingsLogic.actions.initializeRoleMappings(); - - expect(RoleMappingsLogic.values.roleMapping).toEqual(null); - }); - }); - - describe('initializeRoleMapping', () => { - it('sets values for existing mapping', () => { - const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping'); - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - RoleMappingsLogic.actions.initializeRoleMapping(asRoleMapping.id); - - expect(setRoleMappingDataSpy).toHaveBeenCalledWith(asRoleMapping); - }); - - it('does not set data for new mapping', async () => { - const setRoleMappingDataSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping'); - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - RoleMappingsLogic.actions.initializeRoleMapping(); - - expect(setRoleMappingDataSpy).not.toHaveBeenCalledWith(asRoleMapping); - }); - }); - - describe('initializeSingleUserRoleMapping', () => { - let setElasticsearchUserSpy: jest.MockedFunction; - let setRoleMappingSpy: jest.MockedFunction; - let setSingleUserRoleMappingSpy: jest.MockedFunction; - beforeEach(() => { - setElasticsearchUserSpy = jest.spyOn(RoleMappingsLogic.actions, 'setElasticsearchUser'); - setRoleMappingSpy = jest.spyOn(RoleMappingsLogic.actions, 'setRoleMapping'); - setSingleUserRoleMappingSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setSingleUserRoleMapping' - ); - }); - - it('should handle the new user state and only set an empty mapping', () => { - RoleMappingsLogic.actions.initializeSingleUserRoleMapping(); - - expect(setElasticsearchUserSpy).not.toHaveBeenCalled(); - expect(setRoleMappingSpy).not.toHaveBeenCalled(); - expect(setSingleUserRoleMappingSpy).toHaveBeenCalledWith(undefined); - }); - - it('should handle an existing user state and set mapping', () => { - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - RoleMappingsLogic.actions.initializeSingleUserRoleMapping( - asSingleUserRoleMapping.roleMapping.id - ); - - expect(setElasticsearchUserSpy).toHaveBeenCalled(); - expect(setRoleMappingSpy).toHaveBeenCalled(); - expect(setSingleUserRoleMappingSpy).toHaveBeenCalledWith(asSingleUserRoleMapping); - }); - }); - - describe('handleSaveMapping', () => { - const body = { - roleType: 'owner', - accessAllEngines: true, - rules: { - username: '', - }, - engines: [], - }; - - it('calls API and refreshes list when new mapping', async () => { - mount(mappingsServerProps); - const initializeRoleMappingsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'initializeRoleMappings' - ); - - http.post.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.handleSaveMapping(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/role_mappings', { - body: JSON.stringify(body), - }); - await nextTick(); - - expect(initializeRoleMappingsSpy).toHaveBeenCalled(); - }); - - it('calls API and refreshes list when existing mapping', async () => { - mount({ - ...mappingsServerProps, - roleMapping: asRoleMapping, - }); - const initializeRoleMappingsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'initializeRoleMappings' - ); - - http.put.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.handleSaveMapping(); - - expect(http.put).toHaveBeenCalledWith( - `/internal/app_search/role_mappings/${asRoleMapping.id}`, - { - body: JSON.stringify(body), - } - ); - await nextTick(); - - expect(initializeRoleMappingsSpy).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - - it('sends array when "accessAllEngines" is false', () => { - const engine = engines[0]; - - mount({ - ...mappingsServerProps, - accessAllEngines: false, - selectedEngines: new Set([engine.name]), - roleMapping: asRoleMapping, - }); - - http.put.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.handleSaveMapping(); - - expect(http.put).toHaveBeenCalledWith( - `/internal/app_search/role_mappings/${asRoleMapping.id}`, - { - body: JSON.stringify({ - ...body, - accessAllEngines: false, - engines: [engine.name], - }), - } - ); - }); - - it('handles error', async () => { - const setRoleMappingErrorsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setRoleMappingErrors' - ); - - http.post.mockReturnValue( - Promise.reject({ - body: { - attributes: { - errors: ['this is an error'], - }, - }, - }) - ); - RoleMappingsLogic.actions.handleSaveMapping(); - await nextTick(); - - expect(setRoleMappingErrorsSpy).toHaveBeenCalledWith(['this is an error']); - }); - }); - - describe('handleSaveUser', () => { - it('calls API and refreshes list when new mapping', async () => { - const initializeRoleMappingsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'initializeRoleMappings' - ); - const setUserCreatedSpy = jest.spyOn(RoleMappingsLogic.actions, 'setUserCreated'); - const setSingleUserRoleMappingSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setSingleUserRoleMapping' - ); - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - - http.post.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.handleSaveUser(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/single_user_role_mapping', { - body: JSON.stringify({ - roleMapping: { - engines: [], - roleType: 'owner', - accessAllEngines: true, - }, - elasticsearchUser: { - username: elasticsearchUsers[0].username, - email: elasticsearchUsers[0].email, - }, - }), - }); - await nextTick(); - - expect(initializeRoleMappingsSpy).toHaveBeenCalled(); - expect(setUserCreatedSpy).toHaveBeenCalled(); - expect(setSingleUserRoleMappingSpy).toHaveBeenCalled(); - }); - - it('calls API and refreshes list when existing mapping', async () => { - const initializeRoleMappingsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'initializeRoleMappings' - ); - RoleMappingsLogic.actions.setSingleUserRoleMapping(asSingleUserRoleMapping); - RoleMappingsLogic.actions.handleAccessAllEnginesChange(false); - - http.put.mockReturnValue(Promise.resolve(mappingsServerProps)); - RoleMappingsLogic.actions.handleSaveUser(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/single_user_role_mapping', { - body: JSON.stringify({ - roleMapping: { - engines: [], - roleType: 'owner', - accessAllEngines: false, - id: asSingleUserRoleMapping.roleMapping.id, - }, - elasticsearchUser: { - username: '', - email: '', - }, - }), - }); - await nextTick(); - - expect(initializeRoleMappingsSpy).toHaveBeenCalled(); - }); - - it('handles error', async () => { - const setRoleMappingErrorsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setRoleMappingErrors' - ); - - http.post.mockReturnValue( - Promise.reject({ - body: { - attributes: { - errors: ['this is an error'], - }, - }, - }) - ); - RoleMappingsLogic.actions.handleSaveUser(); - await nextTick(); - - expect(setRoleMappingErrorsSpy).toHaveBeenCalledWith(['this is an error']); - }); - }); - - describe('handleDeleteMapping', () => { - const roleMappingId = 'r1'; - - it('calls API and refreshes list', async () => { - mount(mappingsServerProps); - const initializeRoleMappingsSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'initializeRoleMappings' - ); - http.delete.mockReturnValue(Promise.resolve({})); - RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId); - - expect(http.delete).toHaveBeenCalledWith( - `/internal/app_search/role_mappings/${roleMappingId}` - ); - await nextTick(); - - expect(initializeRoleMappingsSpy).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalled(); - }); - - itShowsServerErrorAsFlashMessage(http.delete, () => { - mount(mappingsServerProps); - RoleMappingsLogic.actions.handleDeleteMapping(roleMappingId); - }); - }); - - describe('handleUsernameSelectChange', () => { - it('sets elasticsearchUser when match found', () => { - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - const setElasticsearchUserSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setElasticsearchUser' - ); - RoleMappingsLogic.actions.handleUsernameSelectChange(elasticsearchUsers[0].username); - - expect(setElasticsearchUserSpy).toHaveBeenCalledWith(elasticsearchUsers[0]); - }); - - it('does not set elasticsearchUser when no match found', () => { - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - const setElasticsearchUserSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setElasticsearchUser' - ); - RoleMappingsLogic.actions.handleUsernameSelectChange('bogus'); - - expect(setElasticsearchUserSpy).not.toHaveBeenCalled(); - }); - }); - - describe('setUserExistingRadioValue', () => { - it('handles existing user', () => { - RoleMappingsLogic.actions.setRoleMappingsData(mappingsServerProps); - const setElasticsearchUserSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setElasticsearchUser' - ); - RoleMappingsLogic.actions.setUserExistingRadioValue(true); - - expect(setElasticsearchUserSpy).toHaveBeenCalledWith(elasticsearchUsers[0]); - }); - - it('handles new user', () => { - const setElasticsearchUserSpy = jest.spyOn( - RoleMappingsLogic.actions, - 'setElasticsearchUser' - ); - RoleMappingsLogic.actions.setUserExistingRadioValue(false); - - expect(setElasticsearchUserSpy).toHaveBeenCalledWith(emptyUser); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts deleted file mode 100644 index f4331c5f7534e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.ts +++ /dev/null @@ -1,496 +0,0 @@ -/* - * 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 { - clearFlashMessages, - flashAPIErrors, - flashSuccessToast, -} from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { - RoleMappingsBaseServerDetails, - RoleMappingsBaseActions, - RoleMappingsBaseValues, -} from '../../../shared/role_mapping'; -import { AttributeName, SingleUserRoleMapping, ElasticsearchUser } from '../../../shared/types'; -import { ASRoleMapping, RoleTypes } from '../../types'; -import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines'; -import { Engine } from '../engine/types'; - -import { - ROLE_MAPPING_DELETED_MESSAGE, - ROLE_MAPPING_CREATED_MESSAGE, - ROLE_MAPPING_UPDATED_MESSAGE, -} from './constants'; - -type UserMapping = SingleUserRoleMapping; - -interface RoleMappingsServerDetails extends RoleMappingsBaseServerDetails { - roleMappings: ASRoleMapping[]; - availableEngines: Engine[]; - singleUserRoleMappings: UserMapping[]; - hasAdvancedRoles: boolean; -} - -const getFirstAttributeName = (roleMapping: ASRoleMapping) => - Object.entries(roleMapping.rules)[0][0] as AttributeName; -const getFirstAttributeValue = (roleMapping: ASRoleMapping) => - Object.entries(roleMapping.rules)[0][1] as AttributeName; -const emptyUser = { username: '', email: '' } as ElasticsearchUser; - -interface RoleMappingsActions extends RoleMappingsBaseActions { - setRoleMapping(roleMapping: ASRoleMapping): { roleMapping: ASRoleMapping }; - setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping }; - setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; - handleAccessAllEnginesChange(selected: boolean): { selected: boolean }; - handleEngineSelectionChange(engineNames: string[]): { engineNames: string[] }; - handleRoleChange(roleType: RoleTypes): { roleType: RoleTypes }; -} - -interface RoleMappingsValues extends RoleMappingsBaseValues { - accessAllEngines: boolean; - availableEngines: Engine[]; - roleMapping: ASRoleMapping | null; - roleMappings: ASRoleMapping[]; - singleUserRoleMapping: UserMapping | null; - singleUserRoleMappings: UserMapping[]; - roleType: RoleTypes; - selectedEngines: Set; - hasAdvancedRoles: boolean; -} - -export const RoleMappingsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'users_and_roles'], - actions: { - setRoleMappingsData: (data: RoleMappingsServerDetails) => data, - setRoleMapping: (roleMapping: ASRoleMapping) => ({ roleMapping }), - setElasticsearchUser: (elasticsearchUser: ElasticsearchUser) => ({ elasticsearchUser }), - setSingleUserRoleMapping: (singleUserRoleMapping: UserMapping) => ({ singleUserRoleMapping }), - setRoleMappings: ({ roleMappings }: { roleMappings: ASRoleMapping[] }) => ({ roleMappings }), - setRoleMappingErrors: (errors: string[]) => ({ errors }), - handleRoleChange: (roleType: RoleTypes) => ({ roleType }), - handleUsernameSelectChange: (username: string) => ({ username }), - handleEngineSelectionChange: (engineNames: string[]) => ({ engineNames }), - handleAttributeSelectorChange: (value: string, firstElasticsearchRole: string) => ({ - value, - firstElasticsearchRole, - }), - handleAttributeValueChange: (value: string) => ({ value }), - handleAccessAllEnginesChange: (selected: boolean) => ({ selected }), - enableRoleBasedAccess: true, - openSingleUserRoleMappingFlyout: true, - setUserExistingRadioValue: (userFormUserIsExisting: boolean) => ({ userFormUserIsExisting }), - resetState: true, - initializeRoleMappings: true, - initializeSingleUserRoleMapping: (roleMappingId?: string) => ({ roleMappingId }), - initializeRoleMapping: (roleMappingId) => ({ roleMappingId }), - handleDeleteMapping: (roleMappingId: string) => ({ roleMappingId }), - handleSaveMapping: true, - handleSaveUser: true, - openRoleMappingFlyout: true, - closeUsersAndRolesFlyout: false, - setElasticsearchUsernameValue: (username: string) => ({ username }), - setElasticsearchEmailValue: (email: string) => ({ email }), - setUserCreated: true, - setUserFormIsNewUser: (userFormIsNewUser: boolean) => ({ userFormIsNewUser }), - }, - reducers: { - dataLoading: [ - true, - { - setRoleMappingsData: () => false, - setRoleMappings: () => false, - resetState: () => true, - enableRoleBasedAccess: () => true, - }, - ], - roleMappings: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { roleMappings }) => roleMappings, - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappings: (_, { roleMappings }) => roleMappings, - resetState: () => [], - }, - ], - singleUserRoleMappings: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { singleUserRoleMappings }) => singleUserRoleMappings, - resetState: () => [], - }, - ], - hasAdvancedRoles: [ - false, - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { hasAdvancedRoles }) => hasAdvancedRoles, - }, - ], - availableEngines: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { availableEngines }) => availableEngines, - resetState: () => [], - }, - ], - attributes: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { attributes }) => attributes, - resetState: () => [], - }, - ], - elasticsearchRoles: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { elasticsearchRoles }) => elasticsearchRoles, - }, - ], - elasticsearchUsers: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { elasticsearchUsers }) => elasticsearchUsers, - resetState: () => [], - }, - ], - roleMapping: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMapping: (_, { roleMapping }) => roleMapping, - initializeRoleMappings: () => null, - resetState: () => null, - closeUsersAndRolesFlyout: () => null, - }, - ], - roleType: [ - 'owner', - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMapping: (_, { roleMapping }) => roleMapping.roleType as RoleTypes, - // @ts-expect-error upgrade typescript v5.1.6 - handleRoleChange: (_, { roleType }) => roleType, - }, - ], - accessAllEngines: [ - true, - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMapping: (_, { roleMapping }) => roleMapping.accessAllEngines, - // @ts-expect-error upgrade typescript v5.1.6 - handleRoleChange: (_, { roleType }) => !roleHasScopedEngines(roleType), - // @ts-expect-error upgrade typescript v5.1.6 - handleAccessAllEnginesChange: (_, { selected }) => selected, - closeUsersAndRolesFlyout: () => true, - }, - ], - attributeValue: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMapping: (_, { roleMapping }) => getFirstAttributeValue(roleMapping), - // @ts-expect-error upgrade typescript v5.1.6 - handleAttributeSelectorChange: (_, { value, firstElasticsearchRole }) => - value === 'role' ? firstElasticsearchRole : '', - // @ts-expect-error upgrade typescript v5.1.6 - handleAttributeValueChange: (_, { value }) => value, - resetState: () => '', - closeUsersAndRolesFlyout: () => '', - }, - ], - attributeName: [ - 'username', - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMapping: (_, { roleMapping }) => getFirstAttributeName(roleMapping), - // @ts-expect-error upgrade typescript v5.1.6 - handleAttributeSelectorChange: (_, { value }) => value, - // @ts-expect-error upgrade typescript v5.1.6 - resetState: () => 'username', - // @ts-expect-error upgrade typescript v5.1.6 - closeUsersAndRolesFlyout: () => 'username', - }, - ], - selectedEngines: [ - new Set(), - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMapping: (_, { roleMapping }) => - new Set(roleMapping.engines.map((engine: Engine) => engine.name)), - // @ts-expect-error upgrade typescript v5.1.6 - handleAccessAllEnginesChange: () => new Set(), - // @ts-expect-error upgrade typescript v5.1.6 - handleEngineSelectionChange: (_, { engineNames }) => { - const newSelectedEngineNames = new Set() as Set; - // @ts-expect-error upgrade typescript v5.1.6 - engineNames.forEach((engineName) => newSelectedEngineNames.add(engineName)); - - return newSelectedEngineNames; - }, - // @ts-expect-error upgrade typescript v5.1.6 - closeUsersAndRolesFlyout: () => new Set(), - }, - ], - roleMappingFlyoutOpen: [ - false, - { - openRoleMappingFlyout: () => true, - closeUsersAndRolesFlyout: () => false, - initializeRoleMappings: () => false, - initializeRoleMapping: () => true, - }, - ], - singleUserRoleMappingFlyoutOpen: [ - false, - { - openSingleUserRoleMappingFlyout: () => true, - closeUsersAndRolesFlyout: () => false, - initializeSingleUserRoleMapping: () => true, - }, - ], - singleUserRoleMapping: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - setSingleUserRoleMapping: (_, { singleUserRoleMapping }) => singleUserRoleMapping || null, - closeUsersAndRolesFlyout: () => null, - }, - ], - roleMappingErrors: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingErrors: (_, { errors }) => errors, - handleSaveMapping: () => [], - closeUsersAndRolesFlyout: () => [], - }, - ], - userFormUserIsExisting: [ - true, - { - // @ts-expect-error upgrade typescript v5.1.6 - setUserExistingRadioValue: (_, { userFormUserIsExisting }) => userFormUserIsExisting, - closeUsersAndRolesFlyout: () => true, - }, - ], - elasticsearchUser: [ - emptyUser, - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { elasticsearchUsers }) => elasticsearchUsers[0] || emptyUser, - // @ts-expect-error upgrade typescript v5.1.6 - setElasticsearchUser: (_, { elasticsearchUser }) => elasticsearchUser || emptyUser, - // @ts-expect-error upgrade typescript v5.1.6 - setElasticsearchUsernameValue: (state, { username }) => ({ - ...state, - username, - }), - // @ts-expect-error upgrade typescript v5.1.6 - setElasticsearchEmailValue: (state, { email }) => ({ - ...state, - email, - }), - closeUsersAndRolesFlyout: () => emptyUser, - }, - ], - userCreated: [ - false, - { - setUserCreated: () => true, - closeUsersAndRolesFlyout: () => false, - }, - ], - userFormIsNewUser: [ - true, - { - // @ts-expect-error upgrade typescript v5.1.6 - setUserFormIsNewUser: (_, { userFormIsNewUser }) => userFormIsNewUser, - }, - ], - smtpSettingsPresent: [ - false, - { - // @ts-expect-error upgrade typescript v5.1.6 - setRoleMappingsData: (_, { smtpSettingsPresent }) => smtpSettingsPresent, - }, - ], - formLoading: [ - false, - { - handleSaveMapping: () => true, - handleSaveUser: () => true, - initializeRoleMappings: () => false, - setRoleMappingErrors: () => false, - }, - ], - }, - selectors: ({ selectors }) => ({ - selectedOptions: [ - () => [selectors.selectedEngines, selectors.availableEngines], - (selectedEngines, availableEngines) => { - const selectedNames = Array.from(selectedEngines.values()); - return availableEngines - .filter(({ name }: { name: string }) => selectedNames.includes(name)) - .map(({ name }: { name: string }) => ({ label: name, value: name })); - }, - ], - }), - listeners: ({ actions, values }) => ({ - enableRoleBasedAccess: async () => { - const { http } = HttpLogic.values; - const route = '/internal/app_search/role_mappings/enable_role_based_access'; - - try { - await http.post<{ roleMappings: ASRoleMapping[] }>(route); - actions.initializeRoleMappings(); - } catch (e) { - flashAPIErrors(e); - } - }, - initializeRoleMappings: async () => { - const { http } = HttpLogic.values; - const route = '/internal/app_search/role_mappings'; - - try { - const response = await http.get(route); - actions.setRoleMappingsData(response); - } catch (e) { - flashAPIErrors(e); - } - }, - initializeRoleMapping: async ({ roleMappingId }) => { - const roleMapping = values.roleMappings.find(({ id }) => id === roleMappingId); - if (roleMapping) actions.setRoleMapping(roleMapping); - }, - initializeSingleUserRoleMapping: ({ roleMappingId }) => { - const singleUserRoleMapping = values.singleUserRoleMappings.find( - ({ roleMapping }) => roleMapping.id === roleMappingId - ); - if (singleUserRoleMapping) { - actions.setElasticsearchUser(singleUserRoleMapping.elasticsearchUser); - actions.setRoleMapping(singleUserRoleMapping.roleMapping); - } - actions.setSingleUserRoleMapping(singleUserRoleMapping); - actions.setUserFormIsNewUser(!singleUserRoleMapping); - }, - handleDeleteMapping: async ({ roleMappingId }) => { - const { http } = HttpLogic.values; - const route = `/internal/app_search/role_mappings/${roleMappingId}`; - - try { - await http.delete(route); - actions.initializeRoleMappings(); - flashSuccessToast(ROLE_MAPPING_DELETED_MESSAGE); - } catch (e) { - flashAPIErrors(e); - } - }, - handleSaveMapping: async () => { - const { http } = HttpLogic.values; - - const { - attributeName, - attributeValue, - roleType, - roleMapping, - accessAllEngines, - selectedEngines, - } = values; - - const body = JSON.stringify({ - roleType, - accessAllEngines, - rules: { - [attributeName]: attributeValue, - }, - engines: accessAllEngines ? [] : Array.from(selectedEngines), - }); - - const request = !roleMapping - ? http.post('/internal/app_search/role_mappings', { body }) - : http.put(`/internal/app_search/role_mappings/${roleMapping.id}`, { body }); - - const SUCCESS_MESSAGE = !roleMapping - ? ROLE_MAPPING_CREATED_MESSAGE - : ROLE_MAPPING_UPDATED_MESSAGE; - - try { - await request; - actions.initializeRoleMappings(); - flashSuccessToast(SUCCESS_MESSAGE); - } catch (e) { - actions.setRoleMappingErrors(e?.body?.attributes?.errors); - } - }, - resetState: () => { - clearFlashMessages(); - }, - handleSaveUser: async () => { - const { http } = HttpLogic.values; - const { - roleType, - singleUserRoleMapping, - accessAllEngines, - selectedEngines, - elasticsearchUser: { email, username }, - } = values; - - const body = JSON.stringify({ - roleMapping: { - engines: accessAllEngines ? [] : Array.from(selectedEngines), - roleType, - accessAllEngines, - id: singleUserRoleMapping?.roleMapping?.id, - }, - elasticsearchUser: { - username, - email, - }, - }); - - try { - const response = await http.post( - '/internal/app_search/single_user_role_mapping', - { body } - ); - actions.setSingleUserRoleMapping(response); - actions.setUserCreated(); - actions.initializeRoleMappings(); - } catch (e) { - actions.setRoleMappingErrors(e?.body?.attributes?.errors); - } - }, - closeUsersAndRolesFlyout: () => { - clearFlashMessages(); - const firstUser = values.elasticsearchUsers[0]; - actions.setElasticsearchUser(firstUser); - }, - openRoleMappingFlyout: () => { - clearFlashMessages(); - }, - openSingleUserRoleMappingFlyout: () => { - clearFlashMessages(); - }, - setUserExistingRadioValue: ({ userFormUserIsExisting }) => { - const firstUser = values.elasticsearchUsers[0]; - actions.setElasticsearchUser(userFormUserIsExisting ? firstUser : emptyUser); - }, - handleUsernameSelectChange: ({ username }) => { - const user = values.elasticsearchUsers.find((u) => u.username === username); - if (user) actions.setElasticsearchUser(user); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx deleted file mode 100644 index cec7f1541a31a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.test.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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 '../../../__mocks__/react_router'; -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import { engines } from '../../__mocks__/engines.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { - UserFlyout, - UserAddedInfo, - UserInvitationCallout, - DeactivatedUserCallout, -} from '../../../shared/role_mapping'; -import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; -import { wsSingleUserRoleMapping } from '../../../shared/role_mapping/__mocks__/roles'; - -import { EngineAssignmentSelector } from './engine_assignment_selector'; -import { User } from './user'; - -describe('User', () => { - const handleSaveUser = jest.fn(); - const closeUsersAndRolesFlyout = jest.fn(); - const setUserExistingRadioValue = jest.fn(); - const setElasticsearchUsernameValue = jest.fn(); - const setElasticsearchEmailValue = jest.fn(); - const handleRoleChange = jest.fn(); - const handleUsernameSelectChange = jest.fn(); - - const mockValues = { - availableEngines: [], - singleUserRoleMapping: null, - userFormUserIsExisting: false, - elasticsearchUsers: [], - elasticsearchUser: {}, - roleType: 'admin', - roleMappingErrors: [], - userCreated: false, - userFormIsNewUser: false, - hasAdvancedRoles: false, - }; - - beforeEach(() => { - setMockActions({ - handleSaveUser, - closeUsersAndRolesFlyout, - setUserExistingRadioValue, - setElasticsearchUsernameValue, - setElasticsearchEmailValue, - handleRoleChange, - handleUsernameSelectChange, - }); - - setMockValues(mockValues); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(UserFlyout)).toHaveLength(1); - }); - - it('renders engine assignment selector when groups present', () => { - setMockValues({ ...mockValues, availableEngines: engines, hasAdvancedRoles: true }); - const wrapper = shallow(); - - expect(wrapper.find(EngineAssignmentSelector)).toHaveLength(1); - }); - - it('renders userInvitationCallout', () => { - setMockValues({ - ...mockValues, - singleUserRoleMapping: wsSingleUserRoleMapping, - }); - const wrapper = shallow(); - - expect(wrapper.find(UserInvitationCallout)).toHaveLength(1); - }); - - it('renders user added info when user created', () => { - setMockValues({ - ...mockValues, - singleUserRoleMapping: wsSingleUserRoleMapping, - userCreated: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(UserAddedInfo)).toHaveLength(1); - }); - - it('renders DeactivatedUserCallout', () => { - setMockValues({ - ...mockValues, - singleUserRoleMapping: { - ...wsSingleUserRoleMapping, - invitation: null, - elasticsearchUser: { - ...wsSingleUserRoleMapping.elasticsearchUser, - enabled: false, - }, - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(DeactivatedUserCallout)).toHaveLength(1); - }); - - it('disables form when username value not present', () => { - setMockValues({ - ...mockValues, - singleUserRoleMapping: wsSingleUserRoleMapping, - elasticsearchUsers, - elasticsearchUser: { - username: null, - email: 'email@user.com', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(UserFlyout).prop('disabled')).toEqual(true); - }); - - it('enables form when userFormUserIsExisting', () => { - setMockValues({ - ...mockValues, - userFormUserIsExisting: true.valueOf, - singleUserRoleMapping: wsSingleUserRoleMapping, - elasticsearchUsers, - elasticsearchUser: { - username: null, - email: 'email@user.com', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(UserFlyout).prop('disabled')).toEqual(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx deleted file mode 100644 index 9e041dd83a293..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/user.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 { EuiForm } from '@elastic/eui'; - -import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; -import { - UserFlyout, - UserSelector, - UserAddedInfo, - UserInvitationCallout, - DeactivatedUserCallout, -} from '../../../shared/role_mapping'; -import { RoleTypes } from '../../types'; - -import { EngineAssignmentSelector } from './engine_assignment_selector'; -import { RoleMappingsLogic } from './role_mappings_logic'; - -const standardRoles = ['owner', 'admin'] as unknown as RoleTypes[]; -const advancedRoles = ['dev', 'editor', 'analyst'] as unknown as RoleTypes[]; - -export const User: React.FC = () => { - const { - handleSaveUser, - closeUsersAndRolesFlyout, - setUserExistingRadioValue, - setElasticsearchUsernameValue, - setElasticsearchEmailValue, - handleRoleChange, - handleUsernameSelectChange, - } = useActions(RoleMappingsLogic); - - const { - availableEngines, - singleUserRoleMapping, - hasAdvancedRoles, - userFormUserIsExisting, - elasticsearchUsers, - elasticsearchUser, - roleType, - roleMappingErrors, - userCreated, - userFormIsNewUser, - smtpSettingsPresent, - formLoading, - } = useValues(RoleMappingsLogic); - - const roleTypes = hasAdvancedRoles ? [...standardRoles, ...advancedRoles] : standardRoles; - const hasEngines = availableEngines.length > 0; - const showEngineAssignmentSelector = hasEngines && hasAdvancedRoles; - const flyoutDisabled = - !userFormUserIsExisting && (!elasticsearchUser.email || !elasticsearchUser.username); - const userIsDeactivated = !!( - singleUserRoleMapping && - !singleUserRoleMapping.invitation && - !singleUserRoleMapping.elasticsearchUser.enabled - ); - - const userAddedInfo = singleUserRoleMapping && ( - - ); - - const userInvitationCallout = singleUserRoleMapping?.invitation && ( - - ); - - const createUserForm = ( - 0} error={roleMappingErrors}> - - {showEngineAssignmentSelector && } - - ); - - return ( - - {userCreated ? userAddedInfo : createUserForm} - {userInvitationCallout} - {userIsDeactivated && } - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/i18n.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/i18n.ts deleted file mode 100644 index 229a26b0fb360..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/i18n.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SAMPLE_ENGINE_CREATION_CTA_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.title', - { - defaultMessage: 'Just kicking the tires?', - } -); - -export const SAMPLE_ENGINE_CREATION_CTA_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.description', - { - defaultMessage: 'Test an engine with sample data.', - } -); - -export const SAMPLE_ENGINE_CREATION_CTA_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.sampleEngineCreationCta.buttonLabel', - { - defaultMessage: 'Try a sample engine', - } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/index.ts deleted file mode 100644 index fa11abd19a2db..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SampleEngineCreationCta } from './sample_engine_creation_cta'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.test.tsx deleted file mode 100644 index ac25e887c2de0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 '../../../__mocks__/enterprise_search_url.mock'; -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { SampleEngineCreationCta } from './sample_engine_creation_cta'; - -describe('SampleEngineCTA', () => { - describe('CTA button', () => { - const MOCK_VALUES = { - isLoading: false, - }; - - const MOCK_ACTIONS = { - createSampleEngine: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - }); - - it('calls createSampleEngine on click', () => { - const wrapper = shallow(); - const ctaButton = wrapper.find(EuiButton); - - expect(ctaButton.props().onClick).toEqual(MOCK_ACTIONS.createSampleEngine); - }); - - it('is enabled by default', () => { - const wrapper = shallow(); - const ctaButton = wrapper.find(EuiButton); - - expect(ctaButton.props().isLoading).toEqual(false); - }); - - it('is disabled while loading', () => { - setMockValues({ ...MOCK_VALUES, isLoading: true }); - const wrapper = shallow(); - const ctaButton = wrapper.find(EuiButton); - - expect(ctaButton.props().isLoading).toEqual(true); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx deleted file mode 100644 index 3a292792fbca0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText, EuiButton } from '@elastic/eui'; - -import { - SAMPLE_ENGINE_CREATION_CTA_TITLE, - SAMPLE_ENGINE_CREATION_CTA_DESCRIPTION, - SAMPLE_ENGINE_CREATION_CTA_BUTTON_LABEL, -} from './i18n'; -import { SampleEngineCreationCtaLogic } from './sample_engine_creation_cta_logic'; - -export const SampleEngineCreationCta: React.FC = () => { - const { isLoading } = useValues(SampleEngineCreationCtaLogic); - const { createSampleEngine } = useActions(SampleEngineCreationCtaLogic); - - return ( - - - - -

    {SAMPLE_ENGINE_CREATION_CTA_TITLE}

    -
    - -

    {SAMPLE_ENGINE_CREATION_CTA_DESCRIPTION}

    -
    -
    - - - {SAMPLE_ENGINE_CREATION_CTA_BUTTON_LABEL} - - -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts deleted file mode 100644 index dd469a486ca45..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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, - mockHttpValues, - mockKibanaValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { SampleEngineCreationCtaLogic } from './sample_engine_creation_cta_logic'; - -describe('SampleEngineCreationCtaLogic', () => { - const { mount } = new LogicMounter(SampleEngineCreationCtaLogic); - const { http } = mockHttpValues; - const { navigateToUrl } = mockKibanaValues; - const { flashSuccessToast, flashAPIErrors } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - isLoading: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - mount(); - }); - - it('has expected default values', () => { - expect(SampleEngineCreationCtaLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - it('onSampleEngineCreationFailure sets isLoading to false', () => { - mount({ isLoading: true }); - - SampleEngineCreationCtaLogic.actions.onSampleEngineCreationFailure(); - - expect(SampleEngineCreationCtaLogic.values.isLoading).toEqual(false); - }); - }); - - describe('listeners', () => { - describe('createSampleEngine', () => { - it('POSTS to /internal/app_search/engines', () => { - const body = JSON.stringify({ - seed_sample_engine: true, - }); - SampleEngineCreationCtaLogic.actions.createSampleEngine(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/onboarding_complete', { - body, - }); - }); - - it('calls onSampleEngineCreationSuccess on valid submission', async () => { - jest.spyOn(SampleEngineCreationCtaLogic.actions, 'onSampleEngineCreationSuccess'); - http.post.mockReturnValueOnce(Promise.resolve({})); - - SampleEngineCreationCtaLogic.actions.createSampleEngine(); - await nextTick(); - - expect( - SampleEngineCreationCtaLogic.actions.onSampleEngineCreationSuccess - ).toHaveBeenCalledTimes(1); - }); - - it('calls onSampleEngineCreationFailure and flashAPIErrors on API Error', async () => { - jest.spyOn(SampleEngineCreationCtaLogic.actions, 'onSampleEngineCreationFailure'); - http.post.mockReturnValueOnce(Promise.reject()); - - SampleEngineCreationCtaLogic.actions.createSampleEngine(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - expect( - SampleEngineCreationCtaLogic.actions.onSampleEngineCreationFailure - ).toHaveBeenCalledTimes(1); - }); - }); - - it('onSampleEngineCreationSuccess should show a success message and navigate the user to the engine page', () => { - SampleEngineCreationCtaLogic.actions.onSampleEngineCreationSuccess(); - - expect(flashSuccessToast).toHaveBeenCalledWith("Engine 'national-parks-demo' was created"); - expect(navigateToUrl).toHaveBeenCalledWith('/engines/national-parks-demo'); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.ts deleted file mode 100644 index 6f960abac56f9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 { generatePath } from 'react-router-dom'; - -import { kea, MakeLogicType } from 'kea'; - -import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { KibanaLogic } from '../../../shared/kibana'; -import { ENGINE_PATH } from '../../routes'; -import { ENGINE_CREATION_SUCCESS_MESSAGE } from '../engine_creation/constants'; - -interface SampleEngineCreationCtaActions { - createSampleEngine(): void; - onSampleEngineCreationSuccess(): void; - onSampleEngineCreationFailure(): void; - setIsLoading(isLoading: boolean): { isLoading: boolean }; -} - -interface SampleEngineCreationCtaValues { - isLoading: boolean; -} - -export const SampleEngineCreationCtaLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'sample_engine_cta_logic'], - actions: { - createSampleEngine: true, - onSampleEngineCreationSuccess: true, - onSampleEngineCreationFailure: true, - }, - reducers: { - isLoading: [ - false, - { - createSampleEngine: () => true, - onSampleEngineCreationSuccess: () => false, - onSampleEngineCreationFailure: () => false, - }, - ], - }, - listeners: ({ actions }) => ({ - createSampleEngine: async () => { - const { http } = HttpLogic.values; - - const body = JSON.stringify({ seed_sample_engine: true }); - - try { - await http.post('/internal/app_search/onboarding_complete', { - body, - }); - actions.onSampleEngineCreationSuccess(); - } catch (e) { - actions.onSampleEngineCreationFailure(); - flashAPIErrors(e); - } - }, - onSampleEngineCreationSuccess: () => { - const { navigateToUrl } = KibanaLogic.values; - const enginePath = generatePath(ENGINE_PATH, { engineName: 'national-parks-demo' }); - - flashSuccessToast(ENGINE_CREATION_SUCCESS_MESSAGE('national-parks-demo')); - navigateToUrl(enginePath); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx deleted file mode 100644 index a445b3108c447..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { SchemaAddFieldModal } from '../../../../shared/schema'; - -import { EmptyState } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Create a schema'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchIndexingDocsSchema) - ); - }); - - it('renders a modal that lets a user add a new schema field', () => { - setMockValues({ isModalOpen: true }); - const wrapper = shallow(); - - expect(wrapper.find(SchemaAddFieldModal)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx deleted file mode 100644 index b42fdaef41eaa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/empty_state.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; -import { SchemaAddFieldModal } from '../../../../shared/schema'; -import { SchemaLogic } from '../schema_logic'; - -export const EmptyState: React.FC = () => { - const { isModalOpen } = useValues(SchemaLogic); - const { addSchemaField, closeModal } = useActions(SchemaLogic); - - return ( - <> - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.title', { - defaultMessage: 'Create a schema', - })} - - } - body={ -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.description', { - defaultMessage: - 'Create schema fields in advance, or index some documents and a schema will be created for you.', - })} -

    - } - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel', { - defaultMessage: 'Read the indexing schema guide', - })} - - } - /> - {isModalOpen && ( - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/index.ts deleted file mode 100644 index 6e17547a93980..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 { SchemaCallouts } from './schema_callouts'; -export { SchemaTable } from './schema_table'; -export { EmptyState } from './empty_state'; -export { MetaEnginesSchemaTable } from './meta_engines_schema_table'; -export { MetaEnginesConflictsTable } from './meta_engines_conflicts_table'; -export { TruncatedEnginesList } from './truncated_engines_list'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_conflicts_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_conflicts_table.test.tsx deleted file mode 100644 index cc4d5cfcdb66b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_conflicts_table.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { mount } from 'enzyme'; - -import { EuiTable, EuiTableHeaderCell, EuiTableRow } from '@elastic/eui'; - -import { MetaEnginesConflictsTable } from '.'; - -describe('MetaEnginesConflictsTable', () => { - const values = { - conflictingFields: { - hello_field: { - text: ['engine1'], - number: ['engine2'], - date: ['engine3'], - }, - world_field: { - text: ['engine1'], - location: ['engine2', 'engine3', 'engine4'], - }, - }, - }; - - setMockValues(values); - const wrapper = mount(); - const fieldNames = wrapper.find('EuiTableRowCell[data-test-subj="fieldName"]'); - const fieldTypes = wrapper.find('EuiTableRowCell[data-test-subj="fieldTypes"]'); - const engines = wrapper.find('EuiTableRowCell[data-test-subj="enginesPerFieldType"]'); - - it('renders', () => { - expect(wrapper.find(EuiTable)).toHaveLength(1); - expect(wrapper.find(EuiTableHeaderCell).at(0).text()).toEqual('Field name'); - expect(wrapper.find(EuiTableHeaderCell).at(1).text()).toEqual('Field type conflicts'); - expect(wrapper.find(EuiTableHeaderCell).at(2).text()).toEqual('Engines'); - }); - - it('renders a rowspan on the initial field name column so that it stretches to all associated field conflict rows', () => { - expect(fieldNames).toHaveLength(2); - expect(fieldNames.at(0).prop('rowSpan')).toEqual(3); - expect(fieldNames.at(1).prop('rowSpan')).toEqual(2); - }); - - it('renders a row for each field type conflict and the engines that have that field type', () => { - expect(wrapper.find(EuiTableRow)).toHaveLength(5); - - expect(fieldNames.at(0).text()).toEqual('hello_field'); - expect(fieldTypes.at(0).text()).toEqual('text'); - expect(engines.at(0).text()).toEqual('engine1'); - expect(fieldTypes.at(1).text()).toEqual('number'); - expect(engines.at(1).text()).toEqual('engine2'); - expect(fieldTypes.at(2).text()).toEqual('date'); - expect(engines.at(2).text()).toEqual('engine3'); - - expect(fieldNames.at(1).text()).toEqual('world_field'); - expect(fieldTypes.at(3).text()).toEqual('text'); - expect(engines.at(3).text()).toEqual('engine1'); - expect(fieldTypes.at(4).text()).toEqual('location'); - expect(engines.at(4).text()).toEqual('engine2, engine3, +1'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_conflicts_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_conflicts_table.tsx deleted file mode 100644 index d433015d8de2c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_conflicts_table.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { - EuiTable, - EuiTableHeader, - EuiTableHeaderCell, - EuiTableBody, - EuiTableRow, - EuiTableRowCell, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { FIELD_NAME } from '../../../../shared/schema/constants'; -import { ENGINES_TITLE } from '../../engines'; - -import { MetaEngineSchemaLogic } from '../schema_meta_engine_logic'; - -import { TruncatedEnginesList } from '.'; - -export const MetaEnginesConflictsTable: React.FC = () => { - const { conflictingFields } = useValues(MetaEngineSchemaLogic); - - return ( - - - {FIELD_NAME} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.fieldTypeConflicts', - { defaultMessage: 'Field type conflicts' } - )} - - {ENGINES_TITLE} - - - {Object.entries(conflictingFields).map(([fieldName, conflicts]) => - Object.entries(conflicts).map(([fieldType, engines], i) => { - const isFirstRow = i === 0; - return ( - - {isFirstRow && ( - - {fieldName} - - )} - {fieldType} - - - - - ); - }) - )} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_schema_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_schema_table.test.tsx deleted file mode 100644 index c54a4a5ea32cb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_schema_table.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { mount } from 'enzyme'; - -import { EuiTable, EuiTableHeaderCell, EuiTableRow, EuiTableRowCell } from '@elastic/eui'; - -import { MetaEnginesSchemaTable } from '.'; - -describe('MetaEnginesSchemaTable', () => { - const values = { - schema: { - some_text_field: 'text', - some_number_field: 'number', - }, - fields: { - some_text_field: { - text: ['engine1', 'engine2'], - }, - some_number_field: { - number: ['engine1', 'engine2', 'engine3', 'engine4'], - }, - }, - }; - - setMockValues(values); - const wrapper = mount(); - const fieldNames = wrapper.find('EuiTableRowCell[data-test-subj="fieldName"]'); - const engines = wrapper.find('EuiTableRowCell[data-test-subj="engines"]'); - const fieldTypes = wrapper.find('EuiTableRowCell[data-test-subj="fieldType"]'); - - it('renders', () => { - expect(wrapper.find(EuiTable)).toHaveLength(1); - expect(wrapper.find(EuiTableHeaderCell).at(0).text()).toEqual('Field name'); - expect(wrapper.find(EuiTableHeaderCell).at(1).text()).toEqual('Engines'); - expect(wrapper.find(EuiTableHeaderCell).at(2).text()).toEqual('Field type'); - }); - - it('always renders an initial ID row', () => { - expect(wrapper.find('code').at(0).text()).toEqual('id'); - expect(wrapper.find(EuiTableRowCell).at(1).text()).toEqual('All'); - }); - - it('renders subsequent table rows for each schema field', () => { - expect(wrapper.find(EuiTableRow)).toHaveLength(3); - - expect(fieldNames.at(0).text()).toEqual('some_text_field'); - expect(engines.at(0).text()).toEqual('engine1, engine2'); - expect(fieldTypes.at(0).text()).toEqual('text'); - - expect(fieldNames.at(1).text()).toEqual('some_number_field'); - expect(engines.at(1).text()).toEqual('engine1, engine2, engine3, +1'); - expect(fieldTypes.at(1).text()).toEqual('number'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_schema_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_schema_table.tsx deleted file mode 100644 index 207674438e139..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/meta_engines_schema_table.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { - EuiTable, - EuiTableHeader, - EuiTableHeaderCell, - EuiTableBody, - EuiTableRow, - EuiTableRowCell, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { FIELD_NAME, FIELD_TYPE } from '../../../../shared/schema/constants'; -import { ENGINES_TITLE } from '../../engines'; - -import { MetaEngineSchemaLogic } from '../schema_meta_engine_logic'; - -import { TruncatedEnginesList } from '.'; - -export const MetaEnginesSchemaTable: React.FC = () => { - const { schema, fields } = useValues(MetaEngineSchemaLogic); - - return ( - - - {FIELD_NAME} - {ENGINES_TITLE} - {FIELD_TYPE} - - - - - - id - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.allEngines', - { defaultMessage: 'All' } - )} - - - - - {Object.keys(fields).map((fieldName) => { - const fieldType = schema[fieldName]; - const engines = fields[fieldName][fieldType]; - - return ( - - - {fieldName} - - - - - - {fieldType} - - - ); - })} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_callouts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_callouts.test.tsx deleted file mode 100644 index 21f8662fa3a65..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_callouts.test.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SchemaErrorsCallout } from '../../../../shared/schema'; - -import { - UnsearchedFieldsCallout, - UnconfirmedFieldsCallout, - ConfirmSchemaButton, - MissingSubfieldsCallout, -} from './schema_callouts'; - -import { SchemaCallouts } from '.'; - -describe('SchemaCallouts', () => { - const values = { - hasUnconfirmedFields: false, - hasNewUnsearchedFields: false, - hasIncompleteFields: false, - incompleteFields: [], - mostRecentIndexJob: { - hasErrors: false, - activeReindexJobId: 'some-id', - }, - myRole: { canManageEngines: true }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders nothing if there is nothing to call out', () => { - const wrapper = shallow(); - - expect(wrapper.text()).toBeFalsy(); - }); - - it('renders a schema errors callout if the most recent index job had errors', () => { - setMockValues({ - ...values, - mostRecentIndexJob: { - hasErrors: true, - activeReindexJobId: '12345', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SchemaErrorsCallout)).toHaveLength(1); - expect(wrapper.find(SchemaErrorsCallout).prop('viewErrorsPath')).toEqual( - '/engines/some-engine/schema/reindex_job/12345' - ); - }); - - it('renders an unsearched fields callout if the schema has new unconfirmed & unsearched fields', () => { - setMockValues({ - ...values, - hasUnconfirmedFields: true, - hasNewUnsearchedFields: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(UnsearchedFieldsCallout)).toHaveLength(1); - }); - - it('renders an unconfirmed fields callout if the schema has unconfirmed fields', () => { - setMockValues({ - ...values, - hasUnconfirmedFields: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(UnconfirmedFieldsCallout)).toHaveLength(1); - }); - - it('renders a missing subfields callout if the schema has incomplete fields', () => { - setMockValues({ - ...values, - hasIncompleteFields: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(MissingSubfieldsCallout)).toHaveLength(1); - }); - - describe('non-owner/admins', () => { - it('does not render an unsearched fields callout if user does not have access', () => { - setMockValues({ - ...values, - hasUnconfirmedFields: true, - hasNewUnsearchedFields: true, - myRole: { canManageEngines: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find(UnsearchedFieldsCallout)).toHaveLength(0); - }); - - it('does not render an unconfirmed fields callout if user does not have access', () => { - setMockValues({ - ...values, - hasUnconfirmedFields: true, - myRole: { canManageEngines: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find(UnconfirmedFieldsCallout)).toHaveLength(0); - }); - }); - - describe('UnsearchedFieldsCallout', () => { - it('renders an info callout about unsearched fields with a link to the relevance tuning page', () => { - const wrapper = shallow(); - - expect(wrapper.prop('title')).toEqual( - 'Recently added fields are not being searched by default' - ); - expect(wrapper.find('[data-test-subj="relevanceTuningButtonLink"]').prop('to')).toEqual( - '/engines/some-engine/relevance_tuning' - ); - }); - }); - - describe('UnconfirmedFieldsCallout', () => { - it('renders an info callout about unconfirmed fields', () => { - const wrapper = shallow(); - - expect(wrapper.prop('title')).toEqual("You've recently added new schema fields"); - }); - }); - - describe('ConfirmSchemaButton', () => { - const actions = { updateSchema: jest.fn() }; - - beforeEach(() => { - setMockValues({ isUpdating: false }); - setMockActions(actions); - }); - - it('allows users to confirm schema without changes from the callouts', () => { - const wrapper = shallow(); - - wrapper.simulate('click'); - expect(actions.updateSchema).toHaveBeenCalled(); - }); - }); - - describe('MissingSubfieldsCallout', () => { - it('renders a warning callout about incomplete fields with a link to the subfields support documentation', () => { - const wrapper = shallow(); - - expect(wrapper.prop('title')).toMatch(/^(?:A field is|\d+ fields are) missing subfields$/); - expect(wrapper.find('[data-test-subj="missingSubfieldsLearnMoreLink"]').prop('href')).toEqual( - 'https://www.elastic.co/guide/en/app-search/current/elasticsearch-engines-text-subfields-support-conventions.html' - ); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_callouts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_callouts.tsx deleted file mode 100644 index c50465785f4ad..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_callouts.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiCallOut, EuiButton, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EuiButtonTo } from '../../../../shared/react_router_helpers'; -import { SchemaErrorsCallout } from '../../../../shared/schema'; -import { AppLogic } from '../../../app_logic'; -import { ENGINE_RELEVANCE_TUNING_PATH, ENGINE_REINDEX_JOB_PATH } from '../../../routes'; -import { generateEnginePath } from '../../engine'; - -import { SchemaLogic } from '../schema_logic'; - -export const SchemaCallouts: React.FC = () => { - const { - myRole: { canManageEngines }, - } = useValues(AppLogic); - const { - hasUnconfirmedFields, - hasNewUnsearchedFields, - mostRecentIndexJob: { hasErrors, activeReindexJobId }, - hasIncompleteFields, - } = useValues(SchemaLogic); - - return ( - <> - {hasErrors && ( - <> - - - - )} - {hasUnconfirmedFields && canManageEngines && ( - <> - {hasNewUnsearchedFields ? : } - - - )} - {hasIncompleteFields && ( - <> - - - - )} - - ); -}; - -export const UnsearchedFieldsCallout: React.FC = () => ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.description', - { - defaultMessage: - 'If these new fields should be searchable, update your search settings to include them. If you want them to remain unsearchable, confirm your new field types to dismiss this alert.', - } - )} -

    - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.unsearchedFields.searchSettingsButtonLabel', - { defaultMessage: 'Update search settings' } - )} - - - - - - -
    -); - -export const UnconfirmedFieldsCallout: React.FC = () => ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFields.description', - { - defaultMessage: - 'Set your new schema field(s) to their correct or expected types, and then confirm your field types.', - } - )} -

    - -
    -); - -export const ConfirmSchemaButton: React.FC = () => { - const { updateSchema } = useActions(SchemaLogic); - const { isUpdating } = useValues(SchemaLogic); - - return ( - updateSchema()} - data-test-subj="confirmSchemaTypesButton" - > - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.confirmSchemaButtonLabel', { - defaultMessage: 'Confirm types', - })} - - ); -}; - -export const MissingSubfieldsCallout: React.FC = () => { - const { incompleteFields } = useValues(SchemaLogic); - - return ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.description', - { - defaultMessage: - 'Some fields are missing one or more subfields used by App Search. Some search features may not work until those subfields are added.', - } - )}{' '} -

    - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.incompleteFields.link', { - defaultMessage: 'Learn more.', - })} - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_table.test.tsx deleted file mode 100644 index c5170764f792c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_table.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiTable, EuiTableHeaderCell, EuiTableRow, EuiHealth } from '@elastic/eui'; - -import { SchemaFieldTypeSelect } from '../../../../shared/schema'; - -import { SchemaTable } from '.'; - -describe('SchemaTable', () => { - const values = { - schema: {}, - unconfirmedFields: [], - incompleteFields: [], - myRole: { canManageEngines: true }, - }; - const actions = { - updateSchemaFieldType: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiTable)).toHaveLength(1); - expect(wrapper.find(EuiTableHeaderCell).first().prop('children')).toEqual('Field name'); - expect(wrapper.find(EuiTableHeaderCell).last().prop('children')).toEqual('Field type'); - }); - - it('always renders an initial ID row (with no field type select)', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiTableRow)).toHaveLength(1); - expect(wrapper.find('code').text()).toEqual('id'); - expect(wrapper.find(SchemaFieldTypeSelect)).toHaveLength(0); - }); - - it('renders subsequent table rows for each schema field', () => { - setMockValues({ - ...values, - schema: { - some_text_field: 'text', - some_number_field: 'number', - some_date_field: 'date', - some_location_field: 'geolocation', - }, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiTableRow)).toHaveLength(5); - - expect(wrapper.find('code').at(1).text()).toEqual('some_text_field'); - expect(wrapper.find(SchemaFieldTypeSelect).at(0).prop('fieldType')).toEqual('text'); - - expect(wrapper.find('code').last().text()).toEqual('some_location_field'); - expect(wrapper.find(SchemaFieldTypeSelect).last().prop('fieldType')).toEqual('geolocation'); - }); - - it('renders a recently added status if a field has been recently added', () => { - setMockValues({ - ...values, - schema: { - some_new_field: 'text', - }, - unconfirmedFields: ['some_new_field'], - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiHealth)).toHaveLength(1); - expect(wrapper.find(EuiHealth).childAt(0).prop('children')).toEqual('Recently added'); - }); - - it('disables table actions if access disallowed', () => { - setMockValues({ - ...values, - schema: { - some_text_field: 'text', - }, - myRole: { canManageEngines: false }, - }); - const wrapper = shallow(); - - expect(wrapper.find(SchemaFieldTypeSelect).at(0).prop('disabled')).toEqual(true); - }); - - it('renders a missing subfields status if a field is incomplete', () => { - setMockValues({ - ...values, - schema: { some_incomplete_field: 'text' }, - incompleteFields: ['some_incomplete_field'], - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiHealth)).toHaveLength(1); - expect(wrapper.find(EuiHealth).childAt(0).prop('children')).toEqual('Missing subfields'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_table.tsx deleted file mode 100644 index 7541cc9848cb3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/schema_table.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiTable, - EuiTableHeader, - EuiTableHeaderCell, - EuiTableBody, - EuiTableRow, - EuiTableRowCell, - EuiHealth, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SchemaFieldTypeSelect } from '../../../../shared/schema'; -import { FIELD_NAME, FIELD_TYPE } from '../../../../shared/schema/constants'; - -import { AppLogic } from '../../../app_logic'; - -import { EngineLogic } from '../../engine'; - -import { SchemaLogic } from '../schema_logic'; - -export const SchemaTable: React.FC = () => { - const { - myRole: { canManageEngines }, - } = useValues(AppLogic); - const { schema, unconfirmedFields, incompleteFields } = useValues(SchemaLogic); - const { updateSchemaFieldType } = useActions(SchemaLogic); - const { isElasticsearchEngine } = useValues(EngineLogic); - - return ( - - - {FIELD_NAME} - - - {FIELD_TYPE} - - - - - - - id - - - - - - {Object.entries(schema).map(([fieldName, fieldType]) => { - const isRecentlyAdded = unconfirmedFields.includes(fieldName); - const isMissingSubfields = fieldType === 'text' && incompleteFields.includes(fieldName); - - return ( - - - {fieldName} - - {isRecentlyAdded && ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.unconfirmedFieldLabel', - { defaultMessage: 'Recently added' } - )} - - - - )} - {isMissingSubfields && ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.missingSubfieldsLabel', - { defaultMessage: 'Missing subfields' } - )} - - - - )} - {!(isRecentlyAdded || isMissingSubfields) && } - - - - - ); - })} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/truncated_engines_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/truncated_engines_list.test.tsx deleted file mode 100644 index 34e66e7c496fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/truncated_engines_list.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { TruncatedEnginesList } from '.'; - -describe('TruncatedEnginesList', () => { - it('renders a list of engines with links to their schema pages', () => { - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="displayedEngine"]')).toHaveLength(3); - expect(wrapper.find('[data-test-subj="displayedEngine"]').first().prop('to')).toEqual( - '/engines/engine1/schema' - ); - }); - - it('renders a tooltip when the number of engines is greater than the cutoff', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find('[data-test-subj="displayedEngine"]')).toHaveLength(1); - expect(wrapper.find('[data-test-subj="hiddenEnginesTooltip"]')).toHaveLength(1); - expect(wrapper.find('[data-test-subj="hiddenEnginesTooltip"]').prop('content')).toEqual( - 'engine2, engine3' - ); - }); - - it('does not render if no engines are passed', () => { - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/truncated_engines_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/truncated_engines_list.tsx deleted file mode 100644 index a642eb99e3563..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/components/truncated_engines_list.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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, { Fragment } from 'react'; - -import { EuiText, EuiToolTip, EuiButtonEmpty } from '@elastic/eui'; - -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; -import { ENGINE_SCHEMA_PATH } from '../../../routes'; -import { generateEncodedPath } from '../../../utils/encode_path_params'; - -interface Props { - engines?: string[]; - cutoff?: number; -} - -export const TruncatedEnginesList: React.FC = ({ engines, cutoff = 3 }) => { - if (!engines?.length) return null; - - const displayedEngines = engines.slice(0, cutoff); - const hiddenEngines = engines.slice(cutoff); - const SEPARATOR = ', '; - - return ( - - {displayedEngines.map((engineName, i) => { - const isLast = i === displayedEngines.length - 1; - return ( - - - {engineName} - - {!isLast ? SEPARATOR : ''} - - ); - })} - {hiddenEngines.length > 0 && ( - <> - {SEPARATOR} - - - +{hiddenEngines.length} - - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts deleted file mode 100644 index 0ee428e87873e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/constants.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SCHEMA_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.title', { - defaultMessage: 'Schema', -}); - -export const ADD_SCHEMA_ERROR = (fieldName: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.addSchemaErrorMessage', { - defaultMessage: 'Field name already exists: {fieldName}', - values: { fieldName }, - }); -export const ADD_SCHEMA_SUCCESS = (fieldName: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.addSchemaSuccessMessage', { - defaultMessage: 'New field added: {fieldName}', - values: { fieldName }, - }); -export const UPDATE_SCHEMA_SUCCESS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaSuccessMessage', - { defaultMessage: 'Schema updated' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts deleted file mode 100644 index c0e9ae19e075b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { SCHEMA_TITLE } from './constants'; -export { SchemaRouter } from './schema_router'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/index.ts deleted file mode 100644 index 5ed22298c4862..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { ReindexJob } from './reindex_job'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx deleted file mode 100644 index 4c97cbdf0ed48..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; -import { mockUseParams } from '../../../../__mocks__/react_router'; -import '../../../../__mocks__/shallow_useeffect.mock'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SchemaErrorsAccordion } from '../../../../shared/schema'; - -import { ReindexJob } from '.'; - -describe('ReindexJob', () => { - const values = { - dataLoading: false, - fieldCoercionErrors: {}, - engine: { - schema: { - some_field: 'text', - }, - }, - }; - const actions = { - loadReindexJob: jest.fn(), - }; - - beforeEach(() => { - mockUseParams.mockReturnValueOnce({ reindexJobId: 'abc1234567890' }); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(SchemaErrorsAccordion)).toHaveLength(1); - expect(wrapper.find(SchemaErrorsAccordion).prop('generateViewPath')).toHaveLength(1); - }); - - it('calls loadReindexJob on page load', () => { - shallow(); - - expect(actions.loadReindexJob).toHaveBeenCalledWith('abc1234567890'); - }); - - it('renders schema errors with links to document pages', () => { - const wrapper = shallow(); - const generateViewPath = wrapper - .find(SchemaErrorsAccordion) - .prop('generateViewPath') as Function; - - expect(generateViewPath('some-document-id')).toEqual( - '/engines/some-engine/documents/some-document-id' - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx deleted file mode 100644 index b0a8cbd25f8b0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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, { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; - -import { useActions, useValues } from 'kea'; - -import { i18n } from '@kbn/i18n'; - -import { SchemaErrorsAccordion } from '../../../../shared/schema'; -import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../../routes'; -import { EngineLogic, generateEnginePath, getEngineBreadcrumbs } from '../../engine'; -import { AppSearchPageTemplate } from '../../layout'; -import { SCHEMA_TITLE } from '../constants'; - -import { ReindexJobLogic } from './reindex_job_logic'; - -export const ReindexJob: React.FC = () => { - const { reindexJobId } = useParams() as { reindexJobId: string }; - const { loadReindexJob } = useActions(ReindexJobLogic); - const { dataLoading, fieldCoercionErrors } = useValues(ReindexJobLogic); - const { - engine: { schema }, - } = useValues(EngineLogic); - - useEffect(() => { - loadReindexJob(reindexJobId); - }, [reindexJobId]); - - return ( - - - generateEnginePath(ENGINE_DOCUMENT_DETAIL_PATH, { documentId }) - } - /> - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts deleted file mode 100644 index 67c88d211f118..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../../__mocks__/kea_logic'; -import '../../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { ReindexJobLogic } from './reindex_job_logic'; - -describe('ReindexJobLogic', () => { - const { mount } = new LogicMounter(ReindexJobLogic); - const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; - - const MOCK_RESPONSE = { - fieldCoercionErrors: { - some_erroring_field: [ - { - id: 'document-1', - error: "Value 'some text' cannot be parsed as a number", - }, - ], - another_erroring_field: [ - { - id: 'document-2', - error: "Value '123' cannot be parsed as a date", - }, - ], - }, - }; - - const DEFAULT_VALUES = { - dataLoading: true, - fieldCoercionErrors: {}, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(ReindexJobLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onLoadSuccess', () => { - it('stores fieldCoercionErrors state and sets dataLoading to false', () => { - mount({ fieldCoercionErrors: {}, dataLoading: true }); - - ReindexJobLogic.actions.onLoadSuccess(MOCK_RESPONSE); - - expect(ReindexJobLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - fieldCoercionErrors: MOCK_RESPONSE.fieldCoercionErrors, - }); - }); - }); - - describe('onLoadError', () => { - it('sets dataLoading to false', () => { - mount({ dataLoading: true }); - - ReindexJobLogic.actions.onLoadError(); - - expect(ReindexJobLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadReindexJob', () => { - it('sets dataLoading to true', () => { - mount({ dataLoading: false }); - - ReindexJobLogic.actions.loadReindexJob('some-job-id'); - - expect(ReindexJobLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and then set schema state', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mount(); - jest.spyOn(ReindexJobLogic.actions, 'onLoadSuccess'); - - ReindexJobLogic.actions.loadReindexJob('some-job-id'); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/reindex_job/some-job-id' - ); - expect(ReindexJobLogic.actions.onLoadSuccess).toHaveBeenCalledWith(MOCK_RESPONSE); - }); - - it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); - mount(); - jest.spyOn(ReindexJobLogic.actions, 'onLoadError'); - - ReindexJobLogic.actions.loadReindexJob('some-bad-id'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - expect(ReindexJobLogic.actions.onLoadError).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts deleted file mode 100644 index ce18bfea59256..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../../shared/flash_messages'; -import { HttpLogic } from '../../../../shared/http'; -import { EngineLogic } from '../../engine'; - -import { ReindexJobApiResponse } from '../types'; - -export interface ReindexJobValues { - dataLoading: boolean; - fieldCoercionErrors: ReindexJobApiResponse['fieldCoercionErrors']; -} - -export interface ReindexJobActions { - loadReindexJob(id: string): string; - onLoadSuccess(response: ReindexJobApiResponse): ReindexJobApiResponse; - onLoadError(): void; -} - -export const ReindexJobLogic = kea>({ - path: ['enterprise_search', 'app_search', 'reindex_job_logic'], - actions: { - loadReindexJob: (id) => id, - onLoadSuccess: (response) => response, - onLoadError: true, - }, - reducers: { - dataLoading: [ - true, - { - loadReindexJob: () => true, - onLoadSuccess: () => false, - onLoadError: () => false, - }, - ], - fieldCoercionErrors: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onLoadSuccess: (_, { fieldCoercionErrors }) => fieldCoercionErrors, - }, - ], - }, - listeners: ({ actions }) => ({ - loadReindexJob: async (id) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/reindex_job/${id}` - ); - actions.onLoadSuccess(response); - } catch (e) { - flashAPIErrors(e); - actions.onLoadError(); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts deleted file mode 100644 index e12ad7405742a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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, mockHttpValues } from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { SchemaType } from '../../../shared/schema/types'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { SchemaBaseLogic } from './schema_base_logic'; - -describe('SchemaBaseLogic', () => { - const { mount } = new LogicMounter(SchemaBaseLogic); - const { http } = mockHttpValues; - - const MOCK_SCHEMA = { - some_text_field: SchemaType.Text, - some_number_field: SchemaType.Number, - }; - const MOCK_RESPONSE = { - schema: MOCK_SCHEMA, - } as any; - - const DEFAULT_VALUES = { - dataLoading: true, - schema: {}, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(SchemaBaseLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onSchemaLoad', () => { - it('stores schema state and sets dataLoading to false', () => { - mount({ schema: {}, dataLoading: true }); - - SchemaBaseLogic.actions.onSchemaLoad(MOCK_RESPONSE); - - expect(SchemaBaseLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - schema: MOCK_SCHEMA, - }); - }); - }); - - describe('setSchema', () => { - it('updates schema state', () => { - mount({ schema: {} }); - - SchemaBaseLogic.actions.setSchema(MOCK_SCHEMA); - - expect(SchemaBaseLogic.values).toEqual({ - ...DEFAULT_VALUES, - schema: MOCK_SCHEMA, - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadSchema', () => { - it('sets dataLoading to true', () => { - mount({ dataLoading: false }); - - SchemaBaseLogic.actions.loadSchema(); - - expect(SchemaBaseLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and then set schema state', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mount(); - jest.spyOn(SchemaBaseLogic.actions, 'onSchemaLoad'); - - SchemaBaseLogic.actions.loadSchema(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/schema'); - expect(SchemaBaseLogic.actions.onSchemaLoad).toHaveBeenCalledWith(MOCK_RESPONSE); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - SchemaBaseLogic.actions.loadSchema(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts deleted file mode 100644 index d9dcac5ef158e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { Schema } from '../../../shared/schema/types'; -import { EngineLogic } from '../engine'; - -import { SchemaApiResponse, MetaEngineSchemaApiResponse } from './types'; - -export interface SchemaBaseValues { - dataLoading: boolean; - schema: Schema; -} - -export interface SchemaBaseActions { - loadSchema(): void; - onSchemaLoad( - response: SchemaApiResponse | MetaEngineSchemaApiResponse - ): SchemaApiResponse | MetaEngineSchemaApiResponse; - setSchema(schema: Schema): { schema: Schema }; -} - -export const SchemaBaseLogic = kea>({ - path: ['enterprise_search', 'app_search', 'schema_base_logic'], - actions: { - loadSchema: true, - onSchemaLoad: (response) => response, - setSchema: (schema) => ({ schema }), - }, - reducers: { - dataLoading: [ - true, - { - loadSchema: () => true, - onSchemaLoad: () => false, - }, - ], - schema: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { schema }) => schema, - // @ts-expect-error upgrade typescript v5.1.6 - setSchema: (_, { schema }) => schema, - }, - ], - }, - listeners: ({ actions }) => ({ - loadSchema: async () => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/schema` - ); - actions.onSchemaLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts deleted file mode 100644 index bd7a282fd6c6f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; -import { mockEngineActions } from '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { SchemaType, Schema } from '../../../shared/schema/types'; - -import { SchemaLogic } from './schema_logic'; - -describe('SchemaLogic', () => { - const { mount } = new LogicMounter(SchemaLogic); - const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast, setErrorMessage } = mockFlashMessageHelpers; - - const MOCK_RESPONSE = { - schema: { - some_text_field: SchemaType.Text, - some_number_field: SchemaType.Number, - }, - mostRecentIndexJob: { - percentageComplete: 100, - numDocumentsWithErrors: 10, - activeReindexJobId: 'some-id', - isActive: false, - hasErrors: true, - }, - unconfirmedFields: ['some_field'], - unsearchedUnconfirmedFields: true, - incompleteFields: ['some_other_field'], - }; - - const DEFAULT_VALUES = { - dataLoading: true, - schema: {}, - isUpdating: false, - hasSchema: false, - hasSchemaChanged: false, - cachedSchema: {}, - mostRecentIndexJob: {}, - unconfirmedFields: [], - hasUnconfirmedFields: false, - hasNewUnsearchedFields: false, - isModalOpen: false, - incompleteFields: [], - hasIncompleteFields: false, - }; - - /* - * Unfortunately, we can't mount({ schema: ... }) & have to use an action to set schema - * because of the separate connected logic file - our LogicMounter test helper sets context - * for only the currently tested file - */ - const mountAndSetSchema = ({ schema, ...values }: { schema: Schema; [key: string]: unknown }) => { - mount(values); - SchemaLogic.actions.setSchema(schema); - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(SchemaLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onSchemaLoad', () => { - it('stores the API response in state and sets isUpdating & isModalOpen to false', () => { - mount({ isModalOpen: true }); - - SchemaLogic.actions.onSchemaLoad(MOCK_RESPONSE); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - // SchemaBaseLogic - dataLoading: false, - schema: MOCK_RESPONSE.schema, - // SchemaLogic - isUpdating: false, - isModalOpen: false, - cachedSchema: MOCK_RESPONSE.schema, - hasSchema: true, - mostRecentIndexJob: MOCK_RESPONSE.mostRecentIndexJob, - unconfirmedFields: MOCK_RESPONSE.unconfirmedFields, - hasUnconfirmedFields: true, - hasNewUnsearchedFields: MOCK_RESPONSE.unsearchedUnconfirmedFields, - incompleteFields: MOCK_RESPONSE.incompleteFields, - hasIncompleteFields: true, - }); - }); - }); - - describe('onSchemaUpdateError', () => { - it('sets isUpdating & isModalOpen to false', () => { - mount({ isUpdating: true, isModalOpen: true }); - - SchemaLogic.actions.onSchemaUpdateError(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isUpdating: false, - isModalOpen: false, - }); - }); - }); - - describe('openModal', () => { - it('sets isModalOpen to true', () => { - mount({ isModalOpen: false }); - - SchemaLogic.actions.openModal(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: true, - }); - }); - }); - - describe('closeModal', () => { - it('sets isModalOpen to false', () => { - mount({ isModalOpen: true }); - - SchemaLogic.actions.closeModal(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: false, - }); - }); - }); - }); - - describe('selectors', () => { - describe('hasSchema', () => { - it('returns true when the cached server schema obj has items', () => { - mount({ cachedSchema: { test: SchemaType.Text } }); - expect(SchemaLogic.values.hasSchema).toEqual(true); - }); - - it('returns false when the cached server schema obj is empty', () => { - mount({ schema: {} }); - expect(SchemaLogic.values.hasSchema).toEqual(false); - }); - }); - - describe('hasSchemaChanged', () => { - it('returns true when the schema state is different from the cachedSchema state', () => { - mountAndSetSchema({ - schema: { test: SchemaType.Text }, - cachedSchema: { test: SchemaType.Number }, - }); - - expect(SchemaLogic.values.hasSchemaChanged).toEqual(true); - }); - - it('returns false when the stored schema is the same as cachedSchema', () => { - mountAndSetSchema({ - schema: { test: SchemaType.Text }, - cachedSchema: { test: SchemaType.Text }, - }); - - expect(SchemaLogic.values.hasSchemaChanged).toEqual(false); - }); - }); - - describe('hasUnconfirmedFields', () => { - it('returns true when the unconfirmedFields array has items', () => { - mount({ unconfirmedFields: ['hello_world'] }); - expect(SchemaLogic.values.hasUnconfirmedFields).toEqual(true); - }); - - it('returns false when the unconfirmedFields array is empty', () => { - mount({ unconfirmedFields: [] }); - expect(SchemaLogic.values.hasUnconfirmedFields).toEqual(false); - }); - }); - }); - - describe('listeners', () => { - describe('addSchemaField', () => { - describe('if the schema field already exists', () => { - it('flashes an error and closes the modal', () => { - mountAndSetSchema({ schema: { existing_field: SchemaType.Text } }); - jest.spyOn(SchemaLogic.actions, 'closeModal'); - - SchemaLogic.actions.addSchemaField('existing_field', SchemaType.Text); - - expect(setErrorMessage).toHaveBeenCalledWith('Field name already exists: existing_field'); - expect(SchemaLogic.actions.closeModal).toHaveBeenCalled(); - }); - }); - - describe('if the schema field does not already exist', () => { - it('updates the schema state and calls updateSchema with a custom success message', () => { - mount(); - jest.spyOn(SchemaLogic.actions, 'setSchema'); - jest.spyOn(SchemaLogic.actions, 'updateSchema'); - - SchemaLogic.actions.addSchemaField('new_field', SchemaType.Date); - - expect(SchemaLogic.actions.setSchema).toHaveBeenCalledWith({ - new_field: SchemaType.Date, - }); - expect(SchemaLogic.actions.updateSchema).toHaveBeenCalledWith( - 'New field added: new_field' - ); - }); - }); - }); - - describe('updateSchemaFieldType', () => { - it("updates an existing schema key's field type value", async () => { - mountAndSetSchema({ schema: { existing_field: SchemaType.Text } }); - jest.spyOn(SchemaLogic.actions, 'setSchema'); - - SchemaLogic.actions.updateSchemaFieldType('existing_field', SchemaType.Geolocation); - - expect(SchemaLogic.actions.setSchema).toHaveBeenCalledWith({ - existing_field: SchemaType.Geolocation, - }); - }); - }); - - describe('updateSchema', () => { - it('sets isUpdating to true', () => { - mount({ isUpdating: false }); - - SchemaLogic.actions.updateSchema(); - - expect(SchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - isUpdating: true, - }); - }); - - it('should make an API call and then set schema state', async () => { - http.post.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mount(); - jest.spyOn(SchemaLogic.actions, 'onSchemaLoad'); - - SchemaLogic.actions.updateSchema(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/schema', { - body: '{}', - }); - expect(SchemaLogic.actions.onSchemaLoad).toHaveBeenCalledWith(MOCK_RESPONSE); - }); - - it('should call flashSuccessToast with a custom success message if passed', async () => { - http.post.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mount(); - - SchemaLogic.actions.updateSchema('wow it worked!!'); - await nextTick(); - - expect(flashSuccessToast).toHaveBeenCalledWith('wow it worked!!'); - }); - - it('should always call EngineLogic.actions.initializeEngine to refresh engine-wide state', async () => { - mount(); - - SchemaLogic.actions.updateSchema(); - await nextTick(); - - expect(mockEngineActions.initializeEngine).toHaveBeenCalled(); - }); - - it('handles errors and resets bad schema state back to cached/server values', async () => { - const MOCK_ERROR = 'Fields cannot contain more than 64 characters'; - const MOCK_CACHED_SCHEMA = { ok_field: SchemaType.Text }; - - http.post.mockReturnValueOnce(Promise.reject(MOCK_ERROR)); - mount({ cachedSchema: MOCK_CACHED_SCHEMA }); - jest.spyOn(SchemaLogic.actions, 'onSchemaUpdateError'); - jest.spyOn(SchemaLogic.actions, 'setSchema'); - - SchemaLogic.actions.updateSchema(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith(MOCK_ERROR); - expect(SchemaLogic.actions.onSchemaUpdateError).toHaveBeenCalled(); - expect(SchemaLogic.actions.setSchema).toHaveBeenCalledWith(MOCK_CACHED_SCHEMA); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts deleted file mode 100644 index 418cfa66471f7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.ts +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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 { isEqual } from 'lodash'; - -import { - flashAPIErrors, - setErrorMessage, - flashSuccessToast, - clearFlashMessages, -} from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { Schema, SchemaType, IndexJob } from '../../../shared/schema/types'; -import { EngineLogic } from '../engine'; - -import { ADD_SCHEMA_ERROR, ADD_SCHEMA_SUCCESS, UPDATE_SCHEMA_SUCCESS } from './constants'; -import { SchemaBaseLogic, SchemaBaseValues, SchemaBaseActions } from './schema_base_logic'; -import { SchemaApiResponse } from './types'; - -interface SchemaValues extends SchemaBaseValues { - isUpdating: boolean; - hasSchema: boolean; - hasSchemaChanged: boolean; - cachedSchema: Schema; - mostRecentIndexJob: Partial; - unconfirmedFields: string[]; - hasUnconfirmedFields: boolean; - hasNewUnsearchedFields: boolean; - isModalOpen: boolean; - incompleteFields: string[]; - hasIncompleteFields: boolean; -} - -interface SchemaActions extends SchemaBaseActions { - onSchemaLoad(response: SchemaApiResponse): SchemaApiResponse; - addSchemaField( - fieldName: string, - fieldType: SchemaType - ): { fieldName: string; fieldType: SchemaType }; - updateSchemaFieldType( - fieldName: string, - fieldType: SchemaType - ): { fieldName: string; fieldType: SchemaType }; - updateSchema(successMessage?: string): string | undefined; - onSchemaUpdateError(): void; - openModal(): void; - closeModal(): void; -} - -export const SchemaLogic = kea>({ - path: ['enterprise_search', 'app_search', 'schema_logic'], - connect: { - values: [SchemaBaseLogic, ['dataLoading', 'schema']], - actions: [SchemaBaseLogic, ['loadSchema', 'onSchemaLoad', 'setSchema']], - }, - actions: { - addSchemaField: (fieldName, fieldType) => ({ fieldName, fieldType }), - updateSchemaFieldType: (fieldName, fieldType) => ({ fieldName, fieldType }), - updateSchema: (successMessage) => successMessage, - onSchemaUpdateError: true, - openModal: true, - closeModal: true, - }, - reducers: { - isUpdating: [ - false, - { - updateSchema: () => true, - onSchemaLoad: () => false, - onSchemaUpdateError: () => false, - }, - ], - cachedSchema: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { schema }) => schema, - }, - ], - mostRecentIndexJob: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { mostRecentIndexJob }) => mostRecentIndexJob, - }, - ], - unconfirmedFields: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { unconfirmedFields }) => unconfirmedFields, - }, - ], - incompleteFields: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { incompleteFields }) => incompleteFields, - }, - ], - hasNewUnsearchedFields: [ - false, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { unsearchedUnconfirmedFields }) => unsearchedUnconfirmedFields, - }, - ], - isModalOpen: [ - false, - { - openModal: () => true, - closeModal: () => false, - onSchemaLoad: () => false, - onSchemaUpdateError: () => false, - }, - ], - }, - selectors: { - hasSchema: [ - (selectors) => [selectors.cachedSchema], - (cachedSchema) => Object.keys(cachedSchema).length > 0, - ], - hasSchemaChanged: [ - (selectors) => [selectors.schema, selectors.cachedSchema], - (schema, cachedSchema) => !isEqual(schema, cachedSchema), - ], - hasUnconfirmedFields: [ - (selectors) => [selectors.unconfirmedFields], - (unconfirmedFields) => unconfirmedFields.length > 0, - ], - hasIncompleteFields: [ - (selectors) => [selectors.incompleteFields], - (incompleteFields: string[]) => incompleteFields.length > 0, - ], - }, - listeners: ({ actions, values }) => ({ - addSchemaField: ({ fieldName, fieldType }) => { - if (Object.hasOwn(values.schema, fieldName)) { - setErrorMessage(ADD_SCHEMA_ERROR(fieldName)); - actions.closeModal(); - } else { - const updatedSchema = { ...values.schema }; - updatedSchema[fieldName] = fieldType; - actions.setSchema(updatedSchema); - actions.updateSchema(ADD_SCHEMA_SUCCESS(fieldName)); - } - }, - updateSchemaFieldType: ({ fieldName, fieldType }) => { - const updatedSchema = { ...values.schema }; - updatedSchema[fieldName] = fieldType; - actions.setSchema(updatedSchema); - }, - updateSchema: async (successMessage) => { - const { schema } = values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - clearFlashMessages(); - - try { - const response = await http.post( - `/internal/app_search/engines/${engineName}/schema`, - { body: JSON.stringify(schema) } - ); - actions.onSchemaLoad(response); - flashSuccessToast(successMessage || UPDATE_SCHEMA_SUCCESS); - } catch (e) { - flashAPIErrors(e); - actions.onSchemaUpdateError(); - // Restore updated schema back to server/cached schema, so we don't keep - // erroneous or bad fields in-state - actions.setSchema(values.cachedSchema); - } finally { - // Re-fetch engine data so that other views also dynamically update - // (e.g. Documents results, nav icons for invalid boosts or unconfirmed flags) - EngineLogic.actions.initializeEngine(); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.test.ts deleted file mode 100644 index 4e56b26902142..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 { SchemaType } from '../../../shared/schema/types'; - -import { MetaEngineSchemaLogic } from './schema_meta_engine_logic'; - -describe('MetaEngineSchemaLogic', () => { - const { mount } = new LogicMounter(MetaEngineSchemaLogic); - - const MOCK_RESPONSE = { - schema: { - some_text_field: SchemaType.Text, - some_number_field: SchemaType.Number, - }, - fields: { - some_text_field: { - text: ['source-engine-a', 'source-engine-b'], - }, - }, - conflictingFields: { - some_number_field: { - number: ['source-engine-a'], - text: ['source-engine-b'], - }, - }, - }; - - const DEFAULT_VALUES = { - dataLoading: true, - schema: {}, - fields: {}, - conflictingFields: {}, - conflictingFieldsCount: 0, - hasConflicts: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(MetaEngineSchemaLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onSchemaLoad', () => { - it('stores the API response in state', () => { - mount(); - - MetaEngineSchemaLogic.actions.onSchemaLoad(MOCK_RESPONSE); - - expect(MetaEngineSchemaLogic.values).toEqual({ - ...DEFAULT_VALUES, - // SchemaBaseLogic - dataLoading: false, - schema: MOCK_RESPONSE.schema, - // MetaEngineSchemaLogic - fields: MOCK_RESPONSE.fields, - conflictingFields: MOCK_RESPONSE.conflictingFields, - hasConflicts: true, - conflictingFieldsCount: 1, - }); - }); - }); - }); - - describe('selectors', () => { - describe('conflictingFieldsCount', () => { - it('returns the number of conflicting fields', () => { - mount({ conflictingFields: { field_a: {}, field_b: {} } }); - expect(MetaEngineSchemaLogic.values.conflictingFieldsCount).toEqual(2); - }); - }); - - describe('hasConflictingFields', () => { - it('returns true when the conflictingFields obj has items', () => { - mount({ conflictingFields: { field_c: {} } }); - expect(MetaEngineSchemaLogic.values.hasConflicts).toEqual(true); - }); - - it('returns false when the conflictingFields obj is empty', () => { - mount({ conflictingFields: {} }); - expect(MetaEngineSchemaLogic.values.hasConflicts).toEqual(false); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.ts deleted file mode 100644 index ba7b8b758df9d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_meta_engine_logic.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { SchemaBaseLogic, SchemaBaseValues, SchemaBaseActions } from './schema_base_logic'; -import { MetaEngineSchemaApiResponse } from './types'; - -interface MetaEngineSchemaValues extends SchemaBaseValues { - fields: MetaEngineSchemaApiResponse['fields']; - conflictingFields: MetaEngineSchemaApiResponse['conflictingFields']; - conflictingFieldsCount: number; - hasConflicts: boolean; -} - -interface MetaEngineSchemaActions extends SchemaBaseActions { - onSchemaLoad(response: MetaEngineSchemaApiResponse): MetaEngineSchemaApiResponse; -} - -export const MetaEngineSchemaLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'meta_engine_schema_logic'], - connect: { - values: [SchemaBaseLogic, ['dataLoading', 'schema']], - actions: [SchemaBaseLogic, ['loadSchema', 'onSchemaLoad']], - }, - reducers: { - fields: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { fields }) => fields, - }, - ], - conflictingFields: [ - {}, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSchemaLoad: (_, { conflictingFields }) => conflictingFields, - }, - ], - }, - selectors: { - conflictingFieldsCount: [ - (selectors) => [selectors.conflictingFields], - (conflictingFields) => Object.keys(conflictingFields).length, - ], - hasConflicts: [ - (selectors) => [selectors.conflictingFieldsCount], - (conflictingFieldsCount) => conflictingFieldsCount > 0, - ], - }, -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.test.tsx deleted file mode 100644 index d98ca14a29ec1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { rerender } from '../../../test_helpers'; - -import { ReindexJob } from './reindex_job'; -import { Schema, MetaEngineSchema } from './views'; - -import { SchemaRouter } from '.'; - -describe('SchemaRouter', () => { - const wrapper = shallow(); - - it('renders', () => { - expect(wrapper.find(Routes)).toHaveLength(1); - expect(wrapper.find(Route)).toHaveLength(2); - }); - - it('renders the ReindexJob route', () => { - expect(wrapper.find(ReindexJob)).toHaveLength(1); - }); - - it('renders the MetaEngineSchema view if the current engine is a meta engine', () => { - setMockValues({ isMetaEngine: true }); - rerender(wrapper); - - expect(wrapper.find(MetaEngineSchema)).toHaveLength(1); - expect(wrapper.find(Schema)).toHaveLength(0); - }); - - it('renders the default Schema view if the current engine is not a meta engine', () => { - setMockValues({ isMetaEngine: false }); - rerender(wrapper); - - expect(wrapper.find(Schema)).toHaveLength(1); - expect(wrapper.find(MetaEngineSchema)).toHaveLength(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx deleted file mode 100644 index b3d013ecdf39f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_router.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { ENGINE_REINDEX_JOB_PATH } from '../../routes'; -import { EngineLogic } from '../engine'; - -import { ReindexJob } from './reindex_job'; -import { Schema, MetaEngineSchema } from './views'; - -export const SchemaRouter: React.FC = () => { - const { isMetaEngine } = useValues(EngineLogic); - - return ( - - - - - {isMetaEngine ? : } - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts deleted file mode 100644 index 5f7725b211263..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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, - IndexJob, - SchemaConflicts, - FieldCoercionErrors, -} from '../../../shared/schema/types'; - -export interface SchemaApiResponse { - schema: Schema; - mostRecentIndexJob: IndexJob; - unconfirmedFields: string[]; - unsearchedUnconfirmedFields: boolean; - incompleteFields: string[]; -} - -export interface MetaEngineSchemaApiResponse { - schema: Schema; - fields: SchemaConflicts; - conflictingFields: SchemaConflicts; -} - -export interface ReindexJobApiResponse { - fieldCoercionErrors: FieldCoercionErrors; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/index.ts deleted file mode 100644 index 24f8edd856e48..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { Schema } from './schema'; -export { MetaEngineSchema } from './meta_engine_schema'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx deleted file mode 100644 index c654ce8d9f601..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/shallow_useeffect.mock'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCallOut } from '@elastic/eui'; - -import { MetaEnginesSchemaTable, MetaEnginesConflictsTable } from '../components'; - -import { MetaEngineSchema } from '.'; - -describe('MetaEngineSchema', () => { - const values = { - dataLoading: false, - }; - const actions = { - loadSchema: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(MetaEnginesSchemaTable)).toHaveLength(1); - }); - - it('calls loadSchema on mount', () => { - shallow(); - - expect(actions.loadSchema).toHaveBeenCalled(); - }); - - it('renders an inactive fields callout & table when source engines have schema conflicts', () => { - setMockValues({ ...values, hasConflicts: true, conflictingFieldsCount: 5 }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut)).toHaveLength(1); - expect(wrapper.find(MetaEnginesConflictsTable)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx deleted file mode 100644 index 17ddb34e81e50..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/meta_engine_schema.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DataPanel } from '../../data_panel'; -import { getEngineBreadcrumbs } from '../../engine'; -import { AppSearchPageTemplate } from '../../layout'; - -import { MetaEnginesSchemaTable, MetaEnginesConflictsTable } from '../components'; -import { SCHEMA_TITLE } from '../constants'; -import { MetaEngineSchemaLogic } from '../schema_meta_engine_logic'; - -export const MetaEngineSchema: React.FC = () => { - const { loadSchema } = useActions(MetaEngineSchemaLogic); - const { dataLoading, hasConflicts, conflictingFieldsCount } = useValues(MetaEngineSchemaLogic); - - useEffect(() => { - loadSchema(); - }, []); - - return ( - - {hasConflicts && ( - <> - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.conflictsCalloutDescription', - { - defaultMessage: - 'The field(s) have an inconsistent field-type across the source engines that make up this meta engine. Apply a consistent field-type from the source engines to make these fields searchable.', - } - )} -

    -
    - - - )} - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsTitle', - { defaultMessage: 'Active fields' } - )} - - } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.activeFieldsDescription', - { defaultMessage: 'Fields which belong to one or more engine.' } - )} - > - - - - {hasConflicts && ( - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsTitle', - { defaultMessage: 'Inactive fields' } - )} - - } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.metaEngine.inactiveFieldsDescription', - { - defaultMessage: - 'These fields have type conflicts. To activate these fields, change types in the source engines to match.', - } - )} - > - - - )} -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx deleted file mode 100644 index 7ec8d4731e550..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; -import '../../../../__mocks__/shallow_useeffect.mock'; -import '../../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { SchemaAddFieldModal } from '../../../../shared/schema'; -import { getPageHeaderActions, getPageTitle, getPageDescription } from '../../../../test_helpers'; - -import { SchemaCallouts, SchemaTable } from '../components'; - -import { Schema } from '.'; - -describe('Schema', () => { - const values = { - dataLoading: false, - hasSchema: true, - hasSchemaChanged: false, - isUpdating: false, - isModalOpen: false, - myRole: { canManageEngines: true }, - }; - const actions = { - loadSchema: jest.fn(), - updateSchema: jest.fn(), - addSchemaField: jest.fn(), - openModal: jest.fn(), - closeModal: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(SchemaCallouts)).toHaveLength(1); - expect(wrapper.find(SchemaTable)).toHaveLength(1); - }); - - it('calls loadSchema on mount', () => { - shallow(); - - expect(actions.loadSchema).toHaveBeenCalled(); - }); - - describe('page action buttons', () => { - const subject = () => getPageHeaderActions(shallow()); - - it('renders buttons when access allows', () => { - const wrapper = subject(); - - expect(wrapper.find(EuiButton)).toHaveLength(2); - }); - - it('does not render buttons when access disallowed', () => { - setMockValues({ ...values, myRole: { canManageEngines: false } }); - const wrapper = subject(); - - expect(wrapper.find(EuiButton)).toHaveLength(0); - }); - - it('renders loading/disabled state when schema is updating', () => { - setMockValues({ ...values, isUpdating: true }); - const wrapper = subject(); - - expect(wrapper.find('[data-test-subj="updateSchemaButton"]').prop('isLoading')).toBe(true); - expect(wrapper.find('[data-test-subj="addSchemaFieldModalButton"]').prop('disabled')).toBe( - true - ); - }); - - describe('add button', () => { - it('opens the add schema field modal', () => { - const wrapper = subject(); - - wrapper.find('[data-test-subj="addSchemaFieldModalButton"]').simulate('click'); - expect(actions.openModal).toHaveBeenCalled(); - }); - }); - - describe('update button', () => { - describe('when nothing on the page has changed', () => { - it('is disabled', () => { - const wrapper = subject(); - - expect(wrapper.find('[data-test-subj="updateSchemaButton"]').prop('disabled')).toBe(true); - }); - }); - - describe('when schema has been changed locally', () => { - it('is enabled', () => { - setMockValues({ ...values, hasSchemaChanged: true }); - const wrapper = subject(); - - expect(wrapper.find('[data-test-subj="updateSchemaButton"]').prop('disabled')).toBe( - false - ); - }); - - it('calls updateSchema on click', () => { - setMockValues({ ...values, hasSchemaChanged: true }); - const wrapper = subject(); - - wrapper.find('[data-test-subj="updateSchemaButton"]').simulate('click'); - expect(actions.updateSchema).toHaveBeenCalled(); - }); - }); - }); - }); - - it('renders a modal that lets a user add a new schema field', () => { - setMockValues({ ...values, isModalOpen: true }); - const wrapper = shallow(); - - expect(wrapper.find(SchemaAddFieldModal)).toHaveLength(1); - }); - - it('renders a read-only header for elasticsearch engines', () => { - setMockValues({ ...values, isElasticsearchEngine: true }); - const title = getPageTitle(shallow()); - const description = getPageDescription(shallow()); - - expect(title).toBe('Engine schema'); - expect(description).toBe('View schema field types.'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx deleted file mode 100644 index bc0a001f8bf49..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/views/schema.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SchemaAddFieldModal } from '../../../../shared/schema'; -import { AppLogic } from '../../../app_logic'; -import { EngineLogic, getEngineBreadcrumbs } from '../../engine'; -import { AppSearchPageTemplate } from '../../layout'; - -import { SchemaCallouts, SchemaTable, EmptyState } from '../components'; -import { SCHEMA_TITLE } from '../constants'; -import { SchemaLogic } from '../schema_logic'; - -export const Schema: React.FC = () => { - const { - myRole: { canManageEngines }, - } = useValues(AppLogic); - const { loadSchema, updateSchema, addSchemaField, openModal, closeModal } = - useActions(SchemaLogic); - const { dataLoading, isUpdating, hasSchema, hasSchemaChanged, isModalOpen } = - useValues(SchemaLogic); - const { isElasticsearchEngine } = useValues(EngineLogic); - - useEffect(() => { - loadSchema(); - }, []); - - const schemaActions = [ - updateSchema()} - data-test-subj="updateSchemaButton" - > - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.updateSchemaButtonLabel', { - defaultMessage: 'Save changes', - })} - , - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.createSchemaFieldButtonLabel', - { defaultMessage: 'Create a schema field' } - )} - , - ]; - - const editableSchemaHeader = { - pageTitle: i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.pageTitle', { - defaultMessage: 'Manage engine schema', - }), - description: i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.pageDescription', { - defaultMessage: 'Add new fields or change the types of existing ones.', - }), - rightSideItems: canManageEngines ? schemaActions : [], - }; - - const readOnlySchemaHeader = { - pageTitle: i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageTitle', { - defaultMessage: 'Engine schema', - }), - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.schema.readOnly.pageDescription', - { - defaultMessage: 'View schema field types.', - } - ), - }; - - return ( - } - > - - - {isModalOpen && ( - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/index.ts deleted file mode 100644 index 68cad7b0a0c77..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SearchLogic } from './search_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts deleted file mode 100644 index 3c570de801762..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 '../../__mocks__/engine_logic.mock'; -import { - LogicMounter, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { SearchLogic } from './search_logic'; - -describe('SearchLogic', () => { - const { mount } = new LogicMounter(SearchLogic); - const { http } = mockHttpValues; - const { flashAPIErrors } = mockFlashMessageHelpers; - - const MOCK_SEARCH_RESPONSE = { - results: [ - { id: { raw: 'document-1' }, _meta: { id: 'document-1', engine: 'some-engine' } }, - { id: { raw: 'document-2' }, _meta: { id: 'document-2', engine: 'some-engine' } }, - { id: { raw: 'document-3' }, _meta: { id: 'document-3', engine: 'some-engine' } }, - ], - }; - - const DEFAULT_VALUES = { - searchDataLoading: false, - searchQuery: '', - searchResults: [], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const mountLogic = (values: object = {}) => mount(values, { id: '1' }); - - it('has expected default values', () => { - const logic = mountLogic(); - expect(logic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('search', () => { - it('sets searchQuery & searchDataLoading to true', () => { - const logic = mountLogic({ searchQuery: '', searchDataLoading: false }); - - logic.actions.search('hello world'); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - searchQuery: 'hello world', - searchDataLoading: true, - }); - }); - }); - - describe('onSearch', () => { - it('sets searchResults & searchDataLoading to false', () => { - const logic = mountLogic({ searchResults: [], searchDataLoading: true }); - - logic.actions.onSearch(MOCK_SEARCH_RESPONSE); - - expect(logic.values).toEqual({ - ...DEFAULT_VALUES, - searchResults: MOCK_SEARCH_RESPONSE.results, - searchDataLoading: false, - }); - }); - }); - }); - - describe('listeners', () => { - describe('search', () => { - beforeAll(() => jest.useFakeTimers({ legacyFakeTimers: true })); - afterAll(() => jest.useRealTimers()); - - it('should make a GET API call with a search query', async () => { - http.post.mockReturnValueOnce(Promise.resolve(MOCK_SEARCH_RESPONSE)); - const logic = mountLogic(); - jest.spyOn(logic.actions, 'onSearch'); - - logic.actions.search('hello world'); - jest.runAllTimers(); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/search', { - query: { query: 'hello world' }, - }); - expect(logic.actions.onSearch).toHaveBeenCalledWith(MOCK_SEARCH_RESPONSE); - }); - - it('handles errors', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); - const logic = mountLogic(); - - logic.actions.search('test'); - jest.runAllTimers(); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts deleted file mode 100644 index 13c1ef21c8c56..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 type { SearchResult } from '@elastic/search-ui'; - -import { flashAPIErrors } from '../../../shared/flash_messages'; - -import { HttpLogic } from '../../../shared/http'; -import { formatResult } from '../../utils/results'; -import { EngineLogic } from '../engine'; - -interface SearchValues { - searchDataLoading: boolean; - searchQuery: string; - searchResults: SearchResult[]; -} - -interface SearchActions { - search(query: string): { query: string }; - onSearch({ results }: { results: SearchResult[] }): { results: SearchResult[] }; -} - -export const SearchLogic = kea>({ - key: (props) => props.id, - path: (key: string) => ['enterprise_search', 'app_search', 'search_logic', key], - actions: () => ({ - search: (query) => ({ query }), - onSearch: ({ results }) => ({ results }), - }), - reducers: () => ({ - searchDataLoading: [ - false, - { - search: () => true, - onSearch: () => false, - }, - ], - searchQuery: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - search: (_, { query }) => query, - }, - ], - searchResults: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onSearch: (_, { results }) => results.map((res) => formatResult(res) as SearchResult), - }, - ], - }), - listeners: ({ actions }) => ({ - search: async ({ query }, breakpoint) => { - await breakpoint(250); - - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.post<{ results: SearchResult[] }>( - `/internal/app_search/engines/${engineName}/search`, - { query: { query } } - ); - actions.onSearch(response); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx deleted file mode 100644 index 3466542c09739..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState } from './empty_state'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Add documents to generate a Search UI'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchSearchUI) - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx deleted file mode 100644 index e5b0f2facedbd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -export const EmptyState: React.FC = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.title', { - defaultMessage: 'Add documents to generate a Search UI', - })} - - } - body={ -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.description', { - defaultMessage: - 'A schema will be automatically created for you after you index some documents.', - })} -

    - } - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel', { - defaultMessage: 'Read the Search UI guide', - })} - - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx deleted file mode 100644 index 332f3a3889a32..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.test.tsx +++ /dev/null @@ -1,271 +0,0 @@ -/* - * 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. - */ - -jest.mock('../utils', () => ({ - generatePreviewUrl: jest.fn(), -})); - -import { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiForm } from '@elastic/eui'; - -import { ActiveField } from '../types'; -import { generatePreviewUrl } from '../utils'; - -import { SearchUIForm } from './search_ui_form'; - -describe('SearchUIForm', () => { - const values = { - validFields: ['title', 'url', 'category', 'size'], - validSortFields: ['title', 'url', 'category', 'size'], - validFacetFields: ['title', 'url', 'category', 'size'], - titleField: 'title', - urlField: 'url', - facetFields: ['category'], - sortFields: ['size'], - dataLoading: false, - }; - const actions = { - onActiveFieldChange: jest.fn(), - onFacetFieldsChange: jest.fn(), - onSortFieldsChange: jest.fn(), - onTitleFieldChange: jest.fn(), - onUrlFieldChange: jest.fn(), - onThumbnailFieldChange: jest.fn(), - }; - - beforeAll(() => { - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="selectTitle"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="selectFilters"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="selectSort"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="selectUrl"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="selectThumbnail"]').exists()).toBe(true); - }); - - describe('title field', () => { - beforeEach(() => jest.clearAllMocks()); - const subject = () => shallow().find('[data-test-subj="selectTitle"]'); - - it('renders with its value set from state', () => { - setMockValues({ - ...values, - titleField: 'foo', - }); - - expect(subject().prop('value')).toBe('foo'); - }); - - it('updates state with new value when changed', () => { - subject().simulate('change', { target: { value: 'foo' } }); - expect(actions.onTitleFieldChange).toHaveBeenCalledWith('foo'); - }); - - it('updates active field in state on focus', () => { - subject().simulate('focus'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Title); - }); - - it('removes active field in state on blur', () => { - subject().simulate('blur'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); - }); - }); - - describe('url field', () => { - beforeEach(() => jest.clearAllMocks()); - const subject = () => shallow().find('[data-test-subj="selectUrl"]'); - - it('renders with its value set from state', () => { - setMockValues({ - ...values, - urlField: 'foo', - }); - - expect(subject().prop('value')).toBe('foo'); - }); - - it('updates state with new value when changed', () => { - subject().simulate('change', { target: { value: 'foo' } }); - expect(actions.onUrlFieldChange).toHaveBeenCalledWith('foo'); - }); - - it('updates active field in state on focus', () => { - subject().simulate('focus'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Url); - }); - - it('removes active field in state on blur', () => { - subject().simulate('blur'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); - }); - }); - - describe('thumbnail field', () => { - beforeEach(() => jest.clearAllMocks()); - const subject = () => shallow().find('[data-test-subj="selectThumbnail"]'); - - it('renders with its value set from state', () => { - setMockValues({ - ...values, - thumbnailField: 'foo', - }); - - expect(subject().prop('value')).toBe('foo'); - }); - - it('updates state with new value when changed', () => { - subject().simulate('change', { target: { value: 'foo' } }); - expect(actions.onThumbnailFieldChange).toHaveBeenCalledWith('foo'); - }); - - it('updates active field in state on focus', () => { - subject().simulate('focus'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Thumb); - }); - - it('removes active field in state on blur', () => { - subject().simulate('blur'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); - }); - }); - - describe('filters field', () => { - beforeEach(() => jest.clearAllMocks()); - const subject = () => shallow().find('[data-test-subj="selectFilters"]'); - - it('renders with its value set from state', () => { - setMockValues({ - ...values, - facetFields: ['foo'], - }); - - expect(subject().prop('selectedOptions')).toEqual([ - { label: 'foo', text: 'foo', value: 'foo' }, - ]); - }); - - it('updates state with new value when changed', () => { - subject().simulate('change', [ - { label: 'foo', text: 'foo', value: 'foo' }, - { label: 'bar', text: 'bar', value: 'bar' }, - ]); - expect(actions.onFacetFieldsChange).toHaveBeenCalledWith(['foo', 'bar']); - }); - - it('updates active field in state on focus', () => { - subject().simulate('focus'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Filter); - }); - - it('removes active field in state on blur', () => { - subject().simulate('blur'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); - }); - }); - - describe('sorts field', () => { - beforeEach(() => jest.clearAllMocks()); - const subject = () => shallow().find('[data-test-subj="selectSort"]'); - - it('renders with its value set from state', () => { - setMockValues({ - ...values, - sortFields: ['foo'], - }); - - expect(subject().prop('selectedOptions')).toEqual([ - { label: 'foo', text: 'foo', value: 'foo' }, - ]); - }); - - it('updates state with new value when changed', () => { - subject().simulate('change', [ - { label: 'foo', text: 'foo', value: 'foo' }, - { label: 'bar', text: 'bar', value: 'bar' }, - ]); - expect(actions.onSortFieldsChange).toHaveBeenCalledWith(['foo', 'bar']); - }); - - it('updates active field in state on focus', () => { - subject().simulate('focus'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.Sort); - }); - - it('removes active field in state on blur', () => { - subject().simulate('blur'); - expect(actions.onActiveFieldChange).toHaveBeenCalledWith(ActiveField.None); - }); - }); - - describe('generate preview button', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - (generatePreviewUrl as jest.Mock).mockReturnValue('http://www.example.com?foo=bar'); - setMockValues({ - ...values, - urlField: 'foo', - titleField: 'bar', - facetFields: ['baz'], - sortFields: ['qux'], - searchKey: 'search-123abc', - }); - wrapper = shallow(); - }); - - it('should be a submit button', () => { - expect(wrapper.find('[data-test-subj="generateSearchUiPreview"]').prop('type')).toBe( - 'submit' - ); - }); - - it('should be wrapped in a form configured to POST to the preview screen in a new tab', () => { - const form = wrapper.find(EuiForm); - expect(generatePreviewUrl).toHaveBeenCalledWith({ - urlField: 'foo', - titleField: 'bar', - facets: ['baz'], - sortFields: ['qux'], - }); - expect(form.prop('action')).toBe('http://www.example.com?foo=bar'); - expect(form.prop('target')).toBe('_blank'); - expect(form.prop('method')).toBe('POST'); - expect(form.prop('component')).toBe('form'); - }); - - it('should include a searchKey in that form POST', () => { - const form = wrapper.find(EuiForm); - const hiddenInput = form.find('input[type="hidden"]'); - expect(hiddenInput.prop('id')).toBe('searchKey'); - expect(hiddenInput.prop('value')).toBe('search-123abc'); - }); - }); - - it('should disable everything while data is loading', () => { - setMockValues({ - ...values, - dataLoading: true, - }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="selectTitle"]').prop('disabled')).toBe(true); - expect(wrapper.find('[data-test-subj="selectFilters"]').prop('isDisabled')).toBe(true); - expect(wrapper.find('[data-test-subj="selectSort"]').prop('isDisabled')).toBe(true); - expect(wrapper.find('[data-test-subj="selectUrl"]').prop('disabled')).toBe(true); - expect(wrapper.find('[data-test-subj="generateSearchUiPreview"]').prop('disabled')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx deleted file mode 100644 index 145362812a7bf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_form.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { EuiForm, EuiFormRow, EuiSelect, EuiComboBox, EuiButton } from '@elastic/eui'; - -import { EngineLogic } from '../../engine'; -import { - TITLE_FIELD_LABEL, - TITLE_FIELD_HELP_TEXT, - FILTER_FIELD_LABEL, - FILTER_FIELD_HELP_TEXT, - SORT_FIELD_LABEL, - SORT_FIELD_HELP_TEXT, - URL_FIELD_LABEL, - URL_FIELD_HELP_TEXT, - GENERATE_PREVIEW_BUTTON_LABEL, - THUMBNAIL_FIELD_LABEL, - THUMBNAIL_FIELD_HELP_TEXT, -} from '../i18n'; -import { SearchUILogic } from '../search_ui_logic'; -import { ActiveField } from '../types'; -import { generatePreviewUrl } from '../utils'; - -export const SearchUIForm: React.FC = () => { - const { searchKey } = useValues(EngineLogic); - const { - dataLoading, - validFields, - validSortFields, - validFacetFields, - titleField, - urlField, - thumbnailField, - facetFields, - sortFields, - } = useValues(SearchUILogic); - const { - onActiveFieldChange, - onFacetFieldsChange, - onSortFieldsChange, - onTitleFieldChange, - onUrlFieldChange, - onThumbnailFieldChange, - } = useActions(SearchUILogic); - - const previewHref = generatePreviewUrl({ - titleField, - urlField, - thumbnailField, - facets: facetFields, - sortFields, - }); - - const formatSelectOption = (fieldName: string) => { - return { text: fieldName, value: fieldName }; - }; - const formatMultiOptions = (fieldNames: string[]) => - fieldNames.map((fieldName) => ({ label: fieldName, text: fieldName, value: fieldName })); - const formatMultiOptionsWithEmptyOption = (fieldNames: string[]) => [ - { label: '', text: '', value: '' }, - ...formatMultiOptions(fieldNames), - ]; - - const optionFields = formatMultiOptionsWithEmptyOption(validFields); - const sortOptionFields = formatMultiOptions(validSortFields); - const facetOptionFields = formatMultiOptions(validFacetFields); - const selectedTitleOption = formatSelectOption(titleField); - const selectedURLOption = formatSelectOption(urlField); - const selectedThumbnailOption = formatSelectOption(thumbnailField); - const selectedSortOptions = formatMultiOptions(sortFields); - const selectedFacetOptions = formatMultiOptions(facetFields); - - return ( - - - - onTitleFieldChange(e.target.value)} - fullWidth - onFocus={() => onActiveFieldChange(ActiveField.Title)} - onBlur={() => onActiveFieldChange(ActiveField.None)} - hasNoInitialSelection - data-test-subj="selectTitle" - /> - - - onFacetFieldsChange(newValues.map((field) => field.value!))} - onFocus={() => onActiveFieldChange(ActiveField.Filter)} - onBlur={() => onActiveFieldChange(ActiveField.None)} - fullWidth - data-test-subj="selectFilters" - /> - - - onSortFieldsChange(newValues.map((field) => field.value!))} - onFocus={() => onActiveFieldChange(ActiveField.Sort)} - onBlur={() => onActiveFieldChange(ActiveField.None)} - fullWidth - data-test-subj="selectSort" - /> - - - onUrlFieldChange(e.target.value)} - fullWidth - onFocus={() => onActiveFieldChange(ActiveField.Url)} - onBlur={() => onActiveFieldChange(ActiveField.None)} - hasNoInitialSelection - data-test-subj="selectUrl" - /> - - - onThumbnailFieldChange(e.target.value)} - fullWidth - onFocus={() => onActiveFieldChange(ActiveField.Thumb)} - onBlur={() => onActiveFieldChange(ActiveField.None)} - hasNoInitialSelection - data-test-subj="selectThumbnail" - /> - - - {GENERATE_PREVIEW_BUTTON_LABEL} - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.scss deleted file mode 100644 index 6c97ccddad183..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.scss +++ /dev/null @@ -1,211 +0,0 @@ -.searchUIGraphic { - transform-style: preserve-3d; - transform: rotate3d(0, 1, 0, -8deg); - - #search-area { - .outerBox { - fill: $euiColorLightShade; - } - .field { - fill: $euiColorEmptyShade; - } - .searchIcon { - fill: $euiColorDarkShade; - } - .type { - fill: $euiColorDarkShade; - } - } - #background { - fill: $euiColorLightestShade; - } - #results { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorLightShade; - stroke-width: $euiBorderWidthThin; - } - .shoe { - fill: $euiColorMediumShade; - } - .url { - fill: $euiColorDarkShade; - transform: translateY(5px); - } - .titleCopy { - fill: $euiColorDarkShade; - } - .titleBox { - fill: $euiColorEmptyShade; - } - .blockIn { - fill: $euiColorLightShade; - } - } - #filter { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorLightShade; - stroke-width: 1px; - } - .header { - fill: $euiColorDarkShade; - } - .checkbox { - fill: $euiColorDarkShade; - } - .check { - fill: $euiColorEmptyShade; - } - .filterCopy { - fill: $euiColorDarkShade; - } - } - #sort { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorLightShade; - stroke-width: 1px; - } - .header { - fill: $euiColorDarkShade; - } - .selectCopy { - fill: $euiColorDarkShade; - } - .selectBox { - fill: $euiColorEmptyShade; - stroke: $euiColorLightShade; - stroke-width: 1px; - } - .selectControl { - fill: $euiColorDarkShade; - } - } - #pagination { - .outerBox { - fill: $euiColorLightShade; - } - .arrow { - fill: $euiColorEmptyShade; - } - } - - &.activeTitle { - #results { - .titleBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - stroke-width: 1px; - } - .titleCopy { - fill: $euiColorPrimary; - } - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - } - .url { - fill: $euiColorPrimary; - opacity: .1; - } - .shoe { - fill: $euiColorPrimary; - opacity: .1; - } - } - } - - &.activeUrl { - #results { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - stroke-width: 1px; - } - .url { - fill: $euiColorPrimary; - } - .titleBox { - fill: $euiColorEmptyShade; - } - .titleCopy { - fill: $euiColorPrimary; - opacity: .1; - } - .shoe { - fill: $euiColorPrimary; - opacity: .1; - } - } - } - - &.activeFilter { - #filter { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - stroke-width: $euiBorderWidthThick; - } - .header { - fill: $euiColorPrimary; - } - .checkbox { - fill: $euiColorPrimary; - } - .check { - fill: $euiColorEmptyShade; - } - .filterCopy { - fill: $euiColorPrimary; - } - } - } - - &.activeSort { - #sort { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - stroke-width: 2px; - } - .header { - fill: $euiColorPrimary; - } - .selectCopy { - fill: $euiColorPrimary; - } - .selectBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - stroke-width: 1px; - } - .selectControl { - fill: $euiColorPrimary; - } - } - } - &.activeThumb { - #results { - .outerBox { - fill: $euiColorEmptyShade; - stroke: $euiColorPrimary; - stroke-width: $euiBorderWidthThin; - } - .url { - fill: $euiColorPrimary; - opacity: .1; - } - .titleBox { - fill: $euiColorEmptyShade; - } - .titleCopy { - fill: $euiColorPrimary; - opacity: .1; - } - .shoe { - fill: $euiColorPrimary; - } - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.test.tsx deleted file mode 100644 index 056543fb5e748..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 { setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { ActiveField } from '../types'; - -import { SearchUIGraphic } from './search_ui_graphic'; - -describe('SearchUIGraphic', () => { - const values = { - activeField: ActiveField.Sort, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - }); - - it('renders an svg with a className determined by the currently active field', () => { - const wrapper = shallow(); - expect(wrapper.hasClass('activeSort')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.tsx deleted file mode 100644 index 02b7ac3423227..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/search_ui_graphic.tsx +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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 { useValues } from 'kea'; - -import { SearchUILogic } from '../search_ui_logic'; - -import './search_ui_graphic.scss'; - -export const SearchUIGraphic: React.FC = () => { - const { activeField } = useValues(SearchUILogic); - const svgClass = 'searchUIGraphic'; - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/i18n.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/i18n.ts deleted file mode 100644 index 3e5e3bdfa48cd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/i18n.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { CREDENTIALS_TITLE } from '../credentials'; - -export const SEARCH_UI_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.title', - { defaultMessage: 'Search UI' } -); - -export const TITLE_FIELD_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldLabel', - { defaultMessage: 'Title field (Optional)' } -); -export const TITLE_FIELD_HELP_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.titleFieldHelpText', - { defaultMessage: 'Used as the top-level visual identifier for every rendered result' } -); -export const FILTER_FIELD_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldLabel', - { defaultMessage: 'Filter fields (Optional)' } -); -export const FILTER_FIELD_HELP_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.filterFieldHelpText', - { defaultMessage: 'Faceted values rendered as filters and available as query refinement' } -); -export const SORT_FIELD_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.sortFieldLabel', - { defaultMessage: 'Sort fields (Optional)' } -); -export const SORT_FIELD_HELP_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.sortHelpText', - { defaultMessage: 'Used to display result sorting options, ascending and descending' } -); -export const URL_FIELD_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldLabel', - { defaultMessage: 'URL field (Optional)' } -); -export const THUMBNAIL_FIELD_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldLabel', - { defaultMessage: 'Thumbnail field (Optional)' } -); -export const URL_FIELD_HELP_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.urlFieldHelpText', - { defaultMessage: "Used as a result's link target, if applicable" } -); -export const THUMBNAIL_FIELD_HELP_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.thumbnailFieldHelpText', - { defaultMessage: 'Provide an image URL to show a thumbnail image' } -); -export const GENERATE_PREVIEW_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.searchUI.generatePreviewButtonLabel', - { defaultMessage: 'Generate search experience' } -); -export const NO_SEARCH_KEY_ERROR = (engineName: string) => - i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.noSearchKeyErrorMessage', { - defaultMessage: - "It looks like you don't have any Public Search Keys with access to the ''{engineName}'' engine. Please visit the {credentialsTitle} page to set one up.", - values: { engineName, credentialsTitle: CREDENTIALS_TITLE }, - }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts deleted file mode 100644 index e87606fa04c65..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { SEARCH_UI_TITLE } from './i18n'; -export { SearchUI } from './search_ui'; -export { SearchUILogic } from './search_ui_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.scss deleted file mode 100644 index 85d01408cae96..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.scss +++ /dev/null @@ -1,3 +0,0 @@ -.searchUIGraphicContainer { - transform: translateZ(0); -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.test.tsx deleted file mode 100644 index 0fb691827c4bc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 '../../../__mocks__/shallow_useeffect.mock'; - -import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import { mockEngineValues } from '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { SearchUIForm } from './components/search_ui_form'; -import { SearchUIGraphic } from './components/search_ui_graphic'; - -import { SearchUI } from '.'; - -describe('SearchUI', () => { - const actions = { - loadFieldData: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - setMockValues(mockEngineValues); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(SearchUIForm).exists()).toBe(true); - expect(wrapper.find(SearchUIGraphic).exists()).toBe(true); - }); - - it('initializes data on mount', () => { - shallow(); - expect(actions.loadFieldData).toHaveBeenCalledTimes(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx deleted file mode 100644 index cfef71d34fb9f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { docLinks } from '../../../shared/doc_links'; -import { EngineLogic, getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { EmptyState } from './components/empty_state'; -import { SearchUIForm } from './components/search_ui_form'; -import { SearchUIGraphic } from './components/search_ui_graphic'; -import { SEARCH_UI_TITLE } from './i18n'; -import { SearchUILogic } from './search_ui_logic'; -import './search_ui.scss'; - -export const SearchUI: React.FC = () => { - const { loadFieldData } = useActions(SearchUILogic); - const { hasEmptySchema } = useValues(EngineLogic); - - useEffect(() => { - loadFieldData(); - }, []); - - return ( - } - > - - - -

    - - - - ), - }} - /> -

    -

    - - - - ), - }} - /> -

    -
    - - -
    - - - -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts deleted file mode 100644 index 7e1bbf6cdb84e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; -import { mockEngineValues } from '../../__mocks__'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { ActiveField } from './types'; - -import { SearchUILogic } from '.'; - -describe('SearchUILogic', () => { - const { mount } = new LogicMounter(SearchUILogic); - const { http } = mockHttpValues; - const { setErrorMessage } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - dataLoading: true, - validFields: [], - validSortFields: [], - validFacetFields: [], - titleField: '', - urlField: '', - thumbnailField: '', - facetFields: [], - sortFields: [], - activeField: ActiveField.None, - }; - - beforeEach(() => { - jest.clearAllMocks(); - mockEngineValues.engineName = 'engine1'; - mockEngineValues.searchKey = 'search-abc123'; - }); - - it('has expected default values', () => { - mount(); - expect(SearchUILogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onFieldDataLoaded', () => { - it('sets initial field values fetched from API call and sets dataLoading to false', () => { - mount({ - validFields: [], - validSortFields: [], - validFacetFields: [], - }); - - SearchUILogic.actions.onFieldDataLoaded({ - validFields: ['foo'], - validSortFields: ['bar'], - validFacetFields: ['baz'], - }); - - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - validFields: ['foo'], - validSortFields: ['bar'], - validFacetFields: ['baz'], - }); - }); - }); - - describe('onTitleFieldChange', () => { - it('sets the titleField value', () => { - mount({ titleField: '' }); - SearchUILogic.actions.onTitleFieldChange('foo'); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - titleField: 'foo', - }); - }); - }); - - describe('onUrlFieldChange', () => { - it('sets the urlField value', () => { - mount({ urlField: '' }); - SearchUILogic.actions.onUrlFieldChange('foo'); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - urlField: 'foo', - }); - }); - }); - - describe('onThumbnailFieldChange', () => { - it('sets the thumbnailField value', () => { - mount({ thumbnailField: '' }); - SearchUILogic.actions.onThumbnailFieldChange('foo'); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - thumbnailField: 'foo', - }); - }); - }); - - describe('onFacetFieldsChange', () => { - it('sets the facetFields value', () => { - mount({ facetFields: [] }); - SearchUILogic.actions.onFacetFieldsChange(['foo']); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - facetFields: ['foo'], - }); - }); - }); - - describe('onSortFieldsChange', () => { - it('sets the sortFields value', () => { - mount({ sortFields: [] }); - SearchUILogic.actions.onSortFieldsChange(['foo']); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - sortFields: ['foo'], - }); - }); - }); - - describe('onActiveFieldChange', () => { - it('sets the activeField value', () => { - mount({ activeField: '' }); - SearchUILogic.actions.onActiveFieldChange(ActiveField.Sort); - expect(SearchUILogic.values).toEqual({ - ...DEFAULT_VALUES, - activeField: ActiveField.Sort, - }); - }); - }); - }); - - describe('listeners', () => { - const MOCK_RESPONSE = { - validFields: ['test'], - validSortFields: ['test'], - validFacetFields: ['test'], - defaultValues: { - urlField: 'url', - titleField: 'title', - }, - }; - - describe('loadFieldData', () => { - it('should make an API call and set state based on the response', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_RESPONSE)); - mount(); - jest.spyOn(SearchUILogic.actions, 'onFieldDataLoaded'); - - SearchUILogic.actions.loadFieldData(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/engine1/search_ui/field_config' - ); - expect(SearchUILogic.actions.onFieldDataLoaded).toHaveBeenCalledWith({ - validFields: ['test'], - validSortFields: ['test'], - validFacetFields: ['test'], - urlField: 'url', - titleField: 'title', - }); - }); - - it('will short circuit the call if there is no searchKey available for this engine', async () => { - mockEngineValues.searchKey = ''; - mount(); - - SearchUILogic.actions.loadFieldData(); - - expect(setErrorMessage).toHaveBeenCalledWith( - "It looks like you don't have any Public Search Keys with access to the 'engine1' engine. Please visit the Credentials page to set one up." - ); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - SearchUILogic.actions.loadFieldData(); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts deleted file mode 100644 index 66a6686f741d1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 { flashAPIErrors, setErrorMessage } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { EngineLogic } from '../engine'; - -import { NO_SEARCH_KEY_ERROR } from './i18n'; - -import { ActiveField } from './types'; - -interface InitialFieldValues { - validFields: string[]; - validSortFields: string[]; - validFacetFields: string[]; - urlField?: string; - thumbnailField?: string; - titleField?: string; -} -interface SearchUIActions { - loadFieldData(): void; - onFieldDataLoaded(initialFieldValues: InitialFieldValues): InitialFieldValues; - onActiveFieldChange(activeField: ActiveField): { activeField: ActiveField }; - onFacetFieldsChange(facetFields: string[]): { facetFields: string[] }; - onSortFieldsChange(sortFields: string[]): { sortFields: string[] }; - onTitleFieldChange(titleField: string): { titleField: string }; - onUrlFieldChange(urlField: string): { urlField: string }; - onThumbnailFieldChange(thumbnailField: string): { thumbnailField: string }; -} - -interface SearchUIValues { - dataLoading: boolean; - validFields: string[]; - validSortFields: string[]; - validFacetFields: string[]; - titleField: string; - urlField: string; - thumbnailField: string; - facetFields: string[]; - sortFields: string[]; - activeField: ActiveField; -} - -export const SearchUILogic = kea>({ - path: ['enterprise_search', 'app_search', 'search_ui_logic'], - actions: () => ({ - loadFieldData: () => true, - onFieldDataLoaded: (initialFieldValues) => initialFieldValues, - onActiveFieldChange: (activeField) => ({ activeField }), - onFacetFieldsChange: (facetFields) => ({ facetFields }), - onSortFieldsChange: (sortFields) => ({ sortFields }), - onTitleFieldChange: (titleField) => ({ titleField }), - onUrlFieldChange: (urlField) => ({ urlField }), - onThumbnailFieldChange: (thumbnailField) => ({ thumbnailField }), - }), - // @ts-expect-error upgrade typescript v5.1.6 - reducers: () => ({ - dataLoading: [ - true, - { - onFieldDataLoaded: () => false, - }, - ], - // @ts-expect-error upgrade typescript v5.1.6 - validFields: [[], { onFieldDataLoaded: (_, { validFields }) => validFields }], - // @ts-expect-error upgrade typescript v5.1.6 - validSortFields: [[], { onFieldDataLoaded: (_, { validSortFields }) => validSortFields }], - // @ts-expect-error upgrade typescript v5.1.6 - validFacetFields: [[], { onFieldDataLoaded: (_, { validFacetFields }) => validFacetFields }], - titleField: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - onTitleFieldChange: (_, { titleField }) => titleField, - // @ts-expect-error upgrade typescript v5.1.6 - onFieldDataLoaded: (_, { titleField }) => titleField || '', - }, - ], - urlField: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - onUrlFieldChange: (_, { urlField }) => urlField, - // @ts-expect-error upgrade typescript v5.1.6 - onFieldDataLoaded: (_, { urlField }) => urlField || '', - }, - ], - thumbnailField: [ - '', - { - // @ts-expect-error upgrade typescript v5.1.6 - onThumbnailFieldChange: (_, { thumbnailField }) => thumbnailField, - }, - ], - // @ts-expect-error upgrade typescript v5.1.6 - facetFields: [[], { onFacetFieldsChange: (_, { facetFields }) => facetFields }], - // @ts-expect-error upgrade typescript v5.1.6 - sortFields: [[], { onSortFieldsChange: (_, { sortFields }) => sortFields }], - // @ts-expect-error upgrade typescript v5.1.6 - activeField: [ActiveField.None, { onActiveFieldChange: (_, { activeField }) => activeField }], - }), - listeners: ({ actions }) => ({ - loadFieldData: async () => { - const { http } = HttpLogic.values; - const { searchKey, engineName } = EngineLogic.values; - - if (!searchKey) { - setErrorMessage(NO_SEARCH_KEY_ERROR(engineName)); - return; - } - - const url = `/internal/app_search/engines/${engineName}/search_ui/field_config`; - - try { - const initialFieldValues = await http.get< - InitialFieldValues & { - defaultValues: Pick; - } - >(url); - const { - defaultValues: { urlField, titleField }, - validFields, - validSortFields, - validFacetFields, - } = initialFieldValues; - - actions.onFieldDataLoaded({ - validFields, - validSortFields, - validFacetFields, - urlField, - titleField, - }); - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts deleted file mode 100644 index e5b0f2ce2ccd7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 enum ActiveField { - Title = 'Title', - Filter = 'Filter', - Sort = 'Sort', - Url = 'Url', - Thumb = 'Thumb', - None = '', -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts deleted file mode 100644 index 9a005649e7dc9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 '../../../__mocks__/enterprise_search_url.mock'; - -import { generatePreviewUrl } from './utils'; - -jest.mock('../engine', () => ({ - EngineLogic: { - values: { - engineName: 'national-parks-demo', - }, - }, -})); - -describe('generatePreviewUrl', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('generates a url to the preview application from state', () => { - expect( - generatePreviewUrl({ - titleField: 'foo', - urlField: 'bar', - facets: ['baz', 'qux'], - sortFields: ['quux', 'quuz'], - empty: '', // Empty fields should be stripped - empty2: [''], // Empty fields should be stripped - }) - ).toEqual( - 'http://localhost:3002/as/engines/national-parks-demo/search_experience/preview?facets[]=baz&facets[]=qux&sortFields[]=quux&sortFields[]=quuz&titleField=foo&urlField=bar' - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts deleted file mode 100644 index 6c57df0d66a2a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 queryString, { ParsedQuery } from 'query-string'; - -import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; -import { EngineLogic } from '../engine'; - -export const generatePreviewUrl = (query: ParsedQuery) => { - const { engineName } = EngineLogic.values; - return queryString.stringifyUrl( - { - query, - url: getAppSearchUrl(`/engines/${engineName}/search_experience/preview`), - }, - { arrayFormat: 'bracket', skipEmptyString: true } - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts deleted file mode 100644 index 79de4d59dee63..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SETTINGS_TITLE = i18n.translate('xpack.enterpriseSearch.appSearch.settings.title', { - defaultMessage: 'Settings', -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts deleted file mode 100644 index a9ce92484c8ff..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { Settings } from './settings'; -export { SETTINGS_TITLE } from './constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/generic_confirmation_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/generic_confirmation_modal.test.tsx deleted file mode 100644 index 8477f0e8e2ce2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/generic_confirmation_modal.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { GenericConfirmationModal } from './generic_confirmation_modal'; - -describe('GenericConfirmationModal', () => { - let wrapper: any; - const onClose = jest.fn(); - const onSave = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - wrapper = shallow( - - ); - }); - - it('calls onSave callback when save is pressed', () => { - const button = wrapper.find('[data-test-subj="GenericConfirmationModalSave"]'); - button.simulate('click'); - expect(onSave).toHaveBeenCalled(); - }); - - it('calls onClose callback when Cancel is pressed', () => { - const button = wrapper.find('[data-test-subj="GenericConfirmationModalCancel"]'); - button.simulate('click'); - expect(onClose).toHaveBeenCalled(); - }); - - it('disables the Save button when the input is empty', () => { - const button = wrapper.find('[data-test-subj="GenericConfirmationModalSave"]'); - expect(button.prop('disabled')).toEqual(true); - }); - - it('disables the Save button when the input is not equal to the target', () => { - const input = wrapper.find('[data-test-subj="GenericConfirmationModalInput"]'); - input.prop('onChange')({ - target: { - value: 'NOT_GOOD', - }, - }); - - const button = wrapper.find('[data-test-subj="GenericConfirmationModalSave"]'); - expect(button.prop('disabled')).toEqual(true); - }); - - it('enables the Save button when the current input equals the target prop', () => { - const input = wrapper.find('[data-test-subj="GenericConfirmationModalInput"]'); - input.prop('onChange')({ - target: { - value: 'DISABLE', - }, - }); - const button = wrapper.find('[data-test-subj="GenericConfirmationModalSave"]'); - expect(button.prop('disabled')).toEqual(false); - }); - - it('is not case sensitive', () => { - const input = wrapper.find('[data-test-subj="GenericConfirmationModalInput"]'); - input.prop('onChange')({ - target: { - value: 'diSable', - }, - }); - const button = wrapper.find('[data-test-subj="GenericConfirmationModalSave"]'); - expect(button.prop('disabled')).toEqual(false); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/generic_confirmation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/generic_confirmation_modal.tsx deleted file mode 100644 index a2bbfe16f9fe3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/generic_confirmation_modal.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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, { ReactNode, useState } from 'react'; - -import { - EuiButton, - EuiButtonEmpty, - EuiFieldText, - EuiFormRow, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; - -interface GenericConfirmationModalProps { - description: ReactNode; - subheading: ReactNode; - target: string; - title: string; - onClose(): void; - onSave(): void; -} - -export const GenericConfirmationModal: React.FC = ({ - description, - onClose, - onSave, - subheading, - target, - title, -}) => { - const [inputValue, setInputValue] = useState(''); - - const onConfirm = () => { - setInputValue(''); - onSave(); - }; - - return ( - - - {title} - - - -

    - {subheading} -

    - {description} -
    - - - setInputValue(e.target.value)} - /> - -
    - - - {CANCEL_BUTTON_LABEL} - - - {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.modal.save', { - defaultMessage: 'Save setting', - })} - - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/index.ts deleted file mode 100644 index ca363b29fd2ff..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 { LogRetentionPanel } from './log_retention_panel'; -export { LogRetentionConfirmationModal } from './log_retention_confirmation_modal'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.test.tsx deleted file mode 100644 index d04b6613dc671..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.test.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { LogRetentionOptions } from '../../log_retention'; - -import { GenericConfirmationModal } from './generic_confirmation_modal'; -import { LogRetentionConfirmationModal } from './log_retention_confirmation_modal'; - -describe('', () => { - const actions = { - closeModals: jest.fn(), - saveLogRetention: jest.fn(), - }; - - const values = { - openedModal: null, - logRetention: { - analytics: { - enabled: true, - retentionPolicy: { - isDefault: true, - minAgeDays: 180, - }, - }, - api: { - enabled: true, - retentionPolicy: { - isDefault: true, - minAgeDays: 7, - }, - }, - crawler: { - enabled: true, - retentionPolicy: { - isDefault: true, - minAgeDays: 7, - }, - }, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - setMockValues(values); - }); - - it('renders nothing by default', () => { - const logRetentionPanel = shallow(); - expect(logRetentionPanel.isEmptyRender()).toBe(true); - }); - - describe('analytics', () => { - it('renders the Analytics panel when openedModal is set to Analytics', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.Analytics, - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="AnalyticsLogRetentionConfirmationModal"]').length - ).toBe(1); - }); - - it('calls saveLogRetention on save when showing analytics', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.Analytics, - }); - - const logRetentionPanel = shallow(); - const genericConfirmationModal = logRetentionPanel.find(GenericConfirmationModal); - genericConfirmationModal.prop('onSave')(); - expect(actions.saveLogRetention).toHaveBeenCalledWith(LogRetentionOptions.Analytics, false); - }); - - it('calls closeModals on close', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.Analytics, - }); - - const logRetentionPanel = shallow(); - const genericConfirmationModal = logRetentionPanel.find(GenericConfirmationModal); - genericConfirmationModal.prop('onClose')(); - expect(actions.closeModals).toHaveBeenCalled(); - }); - }); - - describe('api', () => { - it('renders the API panel when openedModal is set to API', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.API, - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="APILogRetentionConfirmationModal"]').length - ).toBe(1); - }); - - it('calls saveLogRetention on save when showing api', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.API, - }); - - const logRetentionPanel = shallow(); - const genericConfirmationModal = logRetentionPanel.find(GenericConfirmationModal); - genericConfirmationModal.prop('onSave')(); - expect(actions.saveLogRetention).toHaveBeenCalledWith(LogRetentionOptions.API, false); - }); - - it('calls closeModals on close', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.API, - }); - - const logRetentionPanel = shallow(); - const genericConfirmationModal = logRetentionPanel.find(GenericConfirmationModal); - genericConfirmationModal.prop('onClose')(); - expect(actions.closeModals).toHaveBeenCalled(); - }); - }); - - describe('crawler', () => { - it('renders the Crawler panel when openedModal is set to Crawler', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.Crawler, - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="CrawlerLogRetentionConfirmationModal"]').length - ).toBe(1); - }); - - it('calls saveLogRetention on save when showing crawler', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.Crawler, - }); - - const logRetentionPanel = shallow(); - const genericConfirmationModal = logRetentionPanel.find(GenericConfirmationModal); - genericConfirmationModal.prop('onSave')(); - expect(actions.saveLogRetention).toHaveBeenCalledWith(LogRetentionOptions.Crawler, false); - }); - - it('calls closeModals on close', () => { - setMockValues({ - ...values, - openedModal: LogRetentionOptions.Crawler, - }); - - const logRetentionPanel = shallow(); - const genericConfirmationModal = logRetentionPanel.find(GenericConfirmationModal); - genericConfirmationModal.prop('onClose')(); - expect(actions.closeModals).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx deleted file mode 100644 index 53a80f6703ef3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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 { EuiTextColor } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { LogRetentionLogic, LogRetentionOptions } from '../../log_retention'; - -import { GenericConfirmationModal } from './generic_confirmation_modal'; - -export const LogRetentionConfirmationModal: React.FC = () => { - const CANNOT_BE_RECOVERED_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.modal.recovery', - { - defaultMessage: 'You cannot recover deleted data.', - } - ); - - const DISABLE_TEXT = i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.modal.disable', - { - defaultMessage: 'DISABLE', - } - ); - - const { closeModals, saveLogRetention } = useActions(LogRetentionLogic); - - const { logRetention, openedModal } = useValues(LogRetentionLogic); - - if (openedModal === null) { - return null; - } - - return ( - <> - {openedModal === LogRetentionOptions.Analytics && ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.modal.analytics.description', - { - defaultMessage: - 'When you disable writing, engines stop logging analytics events. Your existing data is deleted according to the storage time frame.', - } - )} -

    -

    - - {CANNOT_BE_RECOVERED_TEXT} - -

    - - } - target={DISABLE_TEXT} - onClose={closeModals} - onSave={() => saveLogRetention(LogRetentionOptions.Analytics, false)} - /> - )} - {openedModal === LogRetentionOptions.API && ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.modal.api.description', - { - defaultMessage: - 'When you disable writing, engines stop logging API events. Your existing data is deleted according to the storage time frame.', - } - )} -

    -

    - - {CANNOT_BE_RECOVERED_TEXT} - -

    - - } - target={DISABLE_TEXT} - onClose={closeModals} - onSave={() => saveLogRetention(LogRetentionOptions.API, false)} - /> - )} - {openedModal === LogRetentionOptions.Audit && ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.modal.audit.description', - { - defaultMessage: - 'When you disable writing, engines stop logging audit events. Your existing data is deleted according to the storage time frame.', - } - )} -

    -

    - - {CANNOT_BE_RECOVERED_TEXT} - -

    - - } - target={DISABLE_TEXT} - onClose={closeModals} - onSave={() => saveLogRetention(LogRetentionOptions.Audit, false)} - /> - )} - {openedModal === LogRetentionOptions.Crawler && ( - -

    - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.modal.crawler.description', - { - defaultMessage: - 'When you disable writing, engines stop logging Web Crawler events. Your existing data is deleted according to the storage time frame.', - } - )} -

    -

    - - {CANNOT_BE_RECOVERED_TEXT} - -

    - - } - target={DISABLE_TEXT} - onClose={closeModals} - onSave={() => saveLogRetention(LogRetentionOptions.Crawler, false)} - /> - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx deleted file mode 100644 index 0d096727d481c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.test.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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 '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { LogRetention } from '../../log_retention/types'; - -import { LogRetentionPanel } from './log_retention_panel'; - -describe('', () => { - const actions = { - fetchLogRetention: jest.fn(), - toggleLogRetention: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(actions); - }); - - it('renders', () => { - const logRetentionPanel = shallow(); - expect(logRetentionPanel.find('[data-test-subj="LogRetentionPanel"]')).toHaveLength(1); - }); - - it('initializes data on mount', () => { - shallow(); - expect(actions.fetchLogRetention).toHaveBeenCalledTimes(1); - }); - - it('renders Analytics switch off when analytics log retention is false in LogRetentionLogic ', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - analytics: { - enabled: false, - }, - }), - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('checked') - ).toEqual(false); - }); - - it('renders Analytics switch on when analyticsLogRetention is true in LogRetentionLogic ', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - analytics: { - enabled: true, - }, - }), - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('checked') - ).toEqual(true); - }); - - it('renders API switch off when apiLogRetention is false in LogRetentionLogic ', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - api: { - enabled: false, - }, - }), - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('checked') - ).toEqual(false); - }); - - it('renders API switch on when apiLogRetention is true in LogRetentionLogic ', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - api: { - enabled: true, - }, - }), - }); - - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('checked') - ).toEqual(true); - }); - - it('enables all switches when isLogRetentionUpdating is false', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({}), - }); - const logRetentionPanel = shallow(); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('disabled') - ).toEqual(false); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('disabled') - ).toEqual(false); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelCrawlerSwitch"]').prop('disabled') - ).toEqual(false); - }); - - it('disables all switches when isLogRetentionUpdating is true', () => { - setMockValues({ - isLogRetentionUpdating: true, - logRetention: mockLogRetention({}), - }); - const logRetentionPanel = shallow(); - - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]').prop('disabled') - ).toEqual(true); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').prop('disabled') - ).toEqual(true); - expect( - logRetentionPanel.find('[data-test-subj="LogRetentionPanelCrawlerSwitch"]').prop('disabled') - ).toEqual(true); - }); - - it('calls toggleLogRetention when analytics log retention option is changed', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - analytics: { - enabled: false, - }, - }), - }); - const logRetentionPanel = shallow(); - logRetentionPanel - .find('[data-test-subj="LogRetentionPanelAnalyticsSwitch"]') - .simulate('change'); - expect(actions.toggleLogRetention).toHaveBeenCalledWith('analytics'); - }); - - it('calls toggleLogRetention when api log retention option is changed', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - api: { - enabled: false, - }, - }), - }); - const logRetentionPanel = shallow(); - logRetentionPanel.find('[data-test-subj="LogRetentionPanelAPISwitch"]').simulate('change'); - expect(actions.toggleLogRetention).toHaveBeenCalledWith('api'); - }); - - it('calls toggleLogRetention when crawler log retention option is changed', () => { - setMockValues({ - isLogRetentionUpdating: false, - logRetention: mockLogRetention({ - crawler: { - enabled: false, - }, - }), - }); - const logRetentionPanel = shallow(); - logRetentionPanel.find('[data-test-subj="LogRetentionPanelCrawlerSwitch"]').simulate('change'); - expect(actions.toggleLogRetention).toHaveBeenCalledWith('crawler'); - }); -}); - -const mockLogRetention = (logRetention: Partial) => { - const baseLogRetention = { - analytics: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - api: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - crawler: { - disabledAt: null, - enabled: true, - retentionPolicy: { isDefault: true, minAgeDays: 180 }, - }, - }; - - return { - analytics: { - ...baseLogRetention.analytics, - ...logRetention.analytics, - }, - api: { - ...baseLogRetention.api, - ...logRetention.api, - }, - crawler: { - ...baseLogRetention.crawler, - ...logRetention.crawler, - }, - }; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx deleted file mode 100644 index 3d4207d499857..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_panel.tsx +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiPanel, - EuiLink, - EuiSpacer, - EuiSwitch, - EuiText, - EuiTextColor, - EuiTitle, - useGeneratedHtmlId, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { LogRetentionLogic, LogRetentionOptions, LogRetentionMessage } from '../../log_retention'; - -export const LogRetentionPanel: React.FC = () => { - const { toggleLogRetention, fetchLogRetention } = useActions(LogRetentionLogic); - - const { logRetention, isLogRetentionUpdating } = useValues(LogRetentionLogic); - - const hasILM = logRetention !== null; - const analyticsLogRetentionSettings = logRetention?.[LogRetentionOptions.Analytics]; - const apiLogRetentionSettings = logRetention?.[LogRetentionOptions.API]; - const auditLogRetentionSettings = logRetention?.[LogRetentionOptions.Audit]; - const crawlerLogRetentionSettings = logRetention?.[LogRetentionOptions.Crawler]; - const switchPrefixId = useGeneratedHtmlId(); - - useEffect(() => { - fetchLogRetention(); - }, []); - - return ( - - -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.title', { - defaultMessage: 'Log retention', - })} -

    -
    - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.analytics.label', - { - defaultMessage: 'Log analytics events', - } - )} - - {hasILM && ( - <> - {': '} - - - - - )} - - } - checked={!!analyticsLogRetentionSettings?.enabled} - onChange={() => toggleLogRetention(LogRetentionOptions.Analytics)} - disabled={isLogRetentionUpdating} - data-test-subj="LogRetentionPanelAnalyticsSwitch" - /> - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.api.label', - { - defaultMessage: 'Log API events', - } - )} - - {hasILM && ( - <> - {': '} - - - - - )} - - } - checked={!!apiLogRetentionSettings?.enabled} - onChange={() => toggleLogRetention(LogRetentionOptions.API)} - disabled={isLogRetentionUpdating} - data-test-subj="LogRetentionPanelAPISwitch" - /> - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.crawler.label', - { - defaultMessage: 'Web Crawler Logs', - } - )} - - {hasILM && ( - <> - {': '} - - - - - )} - - } - checked={!!crawlerLogRetentionSettings?.enabled} - onChange={() => toggleLogRetention(LogRetentionOptions.Crawler)} - disabled={isLogRetentionUpdating} - data-test-subj="LogRetentionPanelCrawlerSwitch" - /> - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.settings.logRetention.audit.label', - { - defaultMessage: 'Log audit events', - } - )} - - {hasILM && ( - <> - {': '} - - - - {': '} - - )} - - } - checked={!!auditLogRetentionSettings?.enabled} - onChange={() => toggleLogRetention(LogRetentionOptions.Audit)} - disabled={isLogRetentionUpdating} - data-test-subj="LogRetentionPanelAuditSwitch" - /> - - - -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.description', { - defaultMessage: 'Log retention is determined by the ILM policies for your deployment.', - })} -
    - - {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore', { - defaultMessage: 'Learn more about log retention for Enterprise Search.', - })} - -

    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.test.tsx deleted file mode 100644 index 1ad12856a92e1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { LogRetentionPanel } from './log_retention'; - -import { Settings } from './settings'; - -describe('Settings', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(LogRetentionPanel)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx deleted file mode 100644 index 647f4589295d6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/settings.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { AppSearchPageTemplate } from '../layout'; - -import { LogRetentionPanel, LogRetentionConfirmationModal } from './log_retention'; - -import { SETTINGS_TITLE } from '.'; - -export const Settings: React.FC = () => { - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/assets/getting_started.png b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/assets/getting_started.png deleted file mode 100644 index 4d988d14f0483..0000000000000 Binary files a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/assets/getting_started.png and /dev/null differ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts deleted file mode 100644 index 8f952f9406102..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SetupGuide } from './setup_guide'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx deleted file mode 100644 index 9a059e1d2015c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { SetupGuideLayout } from '../../../shared/setup_guide'; - -import { SetupGuide } from '.'; - -describe('SetupGuide', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(SetupGuideLayout)).toHaveLength(1); - expect(wrapper.find(SetPageChrome)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx deleted file mode 100644 index 3bba21361c913..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/setup_guide/setup_guide.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 { EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { SetupGuideLayout, SETUP_GUIDE_TITLE } from '../../../shared/setup_guide'; -import { SendAppSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; - -import GettingStarted from './assets/getting_started.png'; - -export const SetupGuide: React.FC = () => ( - - - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.setupGuide.videoAlt', - - - -

    - -

    -
    - - -

    - -

    -
    -
    -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_button.test.tsx deleted file mode 100644 index fb214b267038f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_button.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { AddSourceEnginesButton } from './add_source_engines_button'; - -describe('AddSourceEnginesButton', () => { - const MOCK_ACTIONS = { - openModal: jest.fn(), - }; - - it('opens the modal on click', () => { - setMockActions(MOCK_ACTIONS); - - const wrapper = shallow(); - const button = wrapper.find(EuiButton); - - expect(button).toHaveLength(1); - - button.simulate('click'); - - expect(MOCK_ACTIONS.openModal).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_button.tsx deleted file mode 100644 index 3076e14d6329b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiButton } from '@elastic/eui'; - -import { ADD_SOURCE_ENGINES_BUTTON_LABEL } from '../i18n'; -import { SourceEnginesLogic } from '../source_engines_logic'; - -export const AddSourceEnginesButton: React.FC = () => { - const { openModal } = useActions(SourceEnginesLogic); - - return ( - - {ADD_SOURCE_ENGINES_BUTTON_LABEL} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_modal.test.tsx deleted file mode 100644 index b02a21ac45aed..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_modal.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton, EuiButtonEmpty, EuiComboBox, EuiModal } from '@elastic/eui'; - -import { AddSourceEnginesModal } from './add_source_engines_modal'; - -describe('AddSourceEnginesModal', () => { - const MOCK_VALUES = { - selectableEngineNames: ['source-engine-1', 'source-engine-2', 'source-engine-3'], - selectedEngineNamesToAdd: ['source-engine-2'], - modalLoading: false, - }; - - const MOCK_ACTIONS = { - addSourceEngines: jest.fn(), - closeModal: jest.fn(), - onAddEnginesSelection: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('calls closeAddSourceEnginesModal when the modal is closed', () => { - const wrapper = shallow(); - wrapper.find(EuiModal).simulate('close'); - - expect(MOCK_ACTIONS.closeModal).toHaveBeenCalled(); - }); - - describe('combo box', () => { - it('has the proper options and selected options', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiComboBox).prop('options')).toEqual([ - { label: 'source-engine-1' }, - { label: 'source-engine-2' }, - { label: 'source-engine-3' }, - ]); - expect(wrapper.find(EuiComboBox).prop('selectedOptions')).toEqual([ - { label: 'source-engine-2' }, - ]); - }); - - it('calls setSelectedEngineNamesToAdd when changed', () => { - const wrapper = shallow(); - wrapper.find(EuiComboBox).simulate('change', [{ label: 'source-engine-3' }]); - - expect(MOCK_ACTIONS.onAddEnginesSelection).toHaveBeenCalledWith(['source-engine-3']); - }); - }); - - describe('cancel button', () => { - it('calls closeModal when clicked', () => { - const wrapper = shallow(); - wrapper.find(EuiButtonEmpty).simulate('click'); - - expect(MOCK_ACTIONS.closeModal).toHaveBeenCalled(); - }); - }); - - describe('save button', () => { - it('is disabled when user has selected no engines', () => { - setMockValues({ - ...MOCK_VALUES, - selectedEngineNamesToAdd: [], - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiButton).prop('disabled')).toEqual(true); - }); - - it('passes modalLoading state', () => { - setMockValues({ - ...MOCK_VALUES, - modalLoading: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(EuiButton).prop('isLoading')).toEqual(true); - }); - - it('calls addSourceEngines when clicked', () => { - const wrapper = shallow(); - wrapper.find(EuiButton).simulate('click'); - - expect(MOCK_ACTIONS.addSourceEngines).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_modal.tsx deleted file mode 100644 index 44dd550d952d9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/add_source_engines_modal.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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, - EuiButtonEmpty, - EuiComboBox, - EuiModalFooter, - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSpacer, - EuiText, -} from '@elastic/eui'; - -import { CANCEL_BUTTON_LABEL, SAVE_BUTTON_LABEL } from '../../../../shared/constants'; - -import { - ADD_SOURCE_ENGINES_MODAL_TITLE, - ADD_SOURCE_ENGINES_MODAL_DESCRIPTION, - ADD_SOURCE_ENGINES_PLACEHOLDER, -} from '../i18n'; -import { SourceEnginesLogic } from '../source_engines_logic'; - -export const AddSourceEnginesModal: React.FC = () => { - const { addSourceEngines, closeModal, onAddEnginesSelection } = useActions(SourceEnginesLogic); - const { selectableEngineNames, selectedEngineNamesToAdd, modalLoading } = - useValues(SourceEnginesLogic); - - return ( - - - {ADD_SOURCE_ENGINES_MODAL_TITLE} - - - {ADD_SOURCE_ENGINES_MODAL_DESCRIPTION} - - ({ label: engineName }))} - selectedOptions={selectedEngineNamesToAdd.map((engineName) => ({ label: engineName }))} - onChange={(options) => onAddEnginesSelection(options.map((option) => option.label))} - placeholder={ADD_SOURCE_ENGINES_PLACEHOLDER} - /> - - - {CANCEL_BUTTON_LABEL} - addSourceEngines(selectedEngineNamesToAdd)} - fill - > - {SAVE_BUTTON_LABEL} - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/index.ts deleted file mode 100644 index edec07a70a0bf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { AddSourceEnginesButton } from './add_source_engines_button'; -export { AddSourceEnginesModal } from './add_source_engines_modal'; -export { SourceEnginesTable } from './source_engines_table'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/source_engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/source_engines_table.test.tsx deleted file mode 100644 index 7638db403191a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/source_engines_table.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiInMemoryTable, EuiButtonIcon } from '@elastic/eui'; - -import { mountWithIntl } from '../../../../test_helpers'; - -import { SourceEnginesTable } from './source_engines_table'; - -describe('SourceEnginesTable', () => { - const MOCK_VALUES = { - // AppLogic - myRole: { - canManageMetaEngineSourceEngines: true, - }, - // SourceEnginesLogic - sourceEngines: [{ name: 'source-engine-1', document_count: 15, field_count: 26 }], - }; - - const MOCK_ACTIONS = { - removeSourceEngine: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiInMemoryTable)).toHaveLength(1); - }); - - it('contains relevant informatiom from source engines', () => { - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiInMemoryTable).text()).toContain('source-engine-1'); - expect(wrapper.find(EuiInMemoryTable).text()).toContain('15'); - expect(wrapper.find(EuiInMemoryTable).text()).toContain('26'); - }); - - describe('actions column', () => { - it('clicking a remove engine link calls a confirm dialogue before remove the engine', () => { - const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValueOnce(true); - const wrapper = mountWithIntl(); - - wrapper.find(EuiButtonIcon).simulate('click'); - - expect(confirmSpy).toHaveBeenCalled(); - expect(MOCK_ACTIONS.removeSourceEngine).toHaveBeenCalled(); - }); - - it('does not remove an engine if the user cancels the confirmation dialog', () => { - const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValueOnce(false); - const wrapper = mountWithIntl(); - - wrapper.find(EuiButtonIcon).simulate('click'); - - expect(confirmSpy).toHaveBeenCalled(); - expect(MOCK_ACTIONS.removeSourceEngine).not.toHaveBeenCalled(); - }); - - it('does not render the actions column if the user does not have permission to manage the engine', () => { - setMockValues({ - ...MOCK_VALUES, - myRole: { canManageMetaEngineSourceEngines: false }, - }); - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiButtonIcon)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/source_engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/source_engines_table.tsx deleted file mode 100644 index f8c3e3ca00c95..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/components/source_engines_table.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui'; - -import { EuiLinkTo } from '../../../../shared/react_router_helpers'; -import { AppLogic } from '../../../app_logic'; -import { ENGINE_PATH } from '../../../routes'; -import { generateEncodedPath } from '../../../utils/encode_path_params'; -import { EngineDetails } from '../../engine/types'; -import { - NAME_COLUMN, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - ACTIONS_COLUMN, -} from '../../engines/components/tables/shared_columns'; - -import { REMOVE_SOURCE_ENGINE_BUTTON_LABEL, REMOVE_SOURCE_ENGINE_CONFIRM_DIALOGUE } from '../i18n'; -import { SourceEnginesLogic } from '../source_engines_logic'; - -export const SourceEnginesTable: React.FC = () => { - const { - myRole: { canManageMetaEngineSourceEngines }, - } = useValues(AppLogic); - - const { removeSourceEngine } = useActions(SourceEnginesLogic); - const { sourceEngines } = useValues(SourceEnginesLogic); - - const columns: Array> = [ - { - ...NAME_COLUMN, - render: (engineName: string) => ( - {engineName} - ), - }, - DOCUMENT_COUNT_COLUMN, - FIELD_COUNT_COLUMN, - ]; - if (canManageMetaEngineSourceEngines) { - columns.push({ - name: ACTIONS_COLUMN.name, - actions: [ - { - name: REMOVE_SOURCE_ENGINE_BUTTON_LABEL, - description: REMOVE_SOURCE_ENGINE_BUTTON_LABEL, - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (engine: EngineDetails) => { - if (confirm(REMOVE_SOURCE_ENGINE_CONFIRM_DIALOGUE(engine.name))) { - removeSourceEngine(engine.name); - } - }, - }, - ], - }); - } - - return ( - 10} - search={{ box: { incremental: true } }} - /> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/i18n.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/i18n.ts deleted file mode 100644 index 402fd15c2024d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/i18n.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SOURCE_ENGINES_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.title', - { defaultMessage: 'Manage engines' } -); - -export const ADD_SOURCE_ENGINES_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesButtonLabel', - { defaultMessage: 'Add engines' } -); - -export const ADD_SOURCE_ENGINES_MODAL_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.title', - { defaultMessage: 'Add engines' } -); - -export const ADD_SOURCE_ENGINES_MODAL_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesModal.description', - { defaultMessage: 'Add additional engines to this meta engine.' } -); - -export const ADD_SOURCE_ENGINES_PLACEHOLDER = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesPlaceholder', - { defaultMessage: 'Select engine(s)' } -); - -export const ADD_SOURCE_ENGINES_SUCCESS_MESSAGE = (sourceEngineNames: string[]) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.addSourceEnginesSuccessMessage', - { - defaultMessage: - '{sourceEnginesCount, plural, one {# engine was} other {# engines were}} added to this meta engine', - values: { sourceEnginesCount: sourceEngineNames.length }, - } - ); - -export const REMOVE_SOURCE_ENGINE_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.sourceEngines.removeEngineButton.label', - { defaultMessage: 'Remove from meta engine' } -); - -export const REMOVE_SOURCE_ENGINE_CONFIRM_DIALOGUE = (engineName: string) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.sourceEngines.removeEngineConfirmDialogue.description', - { - defaultMessage: - 'This will remove the engine, {engineName}, from this meta engine. All existing settings will be lost. Are you sure?', - values: { engineName }, - } - ); - -export const REMOVE_SOURCE_ENGINE_SUCCESS_MESSAGE = (engineName: string) => - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.souceEngines.removeSourceEngineSuccessMessage', - { - defaultMessage: "Engine ''{engineName}'' was removed from this meta engine", - values: { engineName }, - } - ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/index.ts deleted file mode 100644 index 5f85fba54d8e7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { SourceEngines } from './source_engines'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines.test.tsx deleted file mode 100644 index e2398209e630d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { getPageHeaderActions } from '../../../test_helpers'; - -import { AddSourceEnginesButton, AddSourceEnginesModal, SourceEnginesTable } from './components'; - -import { SourceEngines } from '.'; - -describe('SourceEngines', () => { - const MOCK_ACTIONS = { - fetchIndexedEngines: jest.fn(), - fetchSourceEngines: jest.fn(), - }; - - const MOCK_VALUES = { - // AppLogic - myRole: { - canManageMetaEngineSourceEngines: true, - }, - // SourceEnginesLogic - dataLoading: false, - isModalOpen: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(MOCK_VALUES); - setMockActions(MOCK_ACTIONS); - }); - - it('renders and calls a function to initialize data', () => { - const wrapper = shallow(); - - expect(wrapper.find(SourceEnginesTable)).toHaveLength(1); - expect(MOCK_ACTIONS.fetchIndexedEngines).toHaveBeenCalled(); - expect(MOCK_ACTIONS.fetchSourceEngines).toHaveBeenCalled(); - }); - - it('renders the add source engines modal', () => { - setMockValues({ - ...MOCK_VALUES, - isModalOpen: true, - }); - const wrapper = shallow(); - - expect(wrapper.find(AddSourceEnginesModal)).toHaveLength(1); - }); - - describe('page actions', () => { - it('contains a button to add source engines', () => { - const wrapper = shallow(); - expect(getPageHeaderActions(wrapper).find(AddSourceEnginesButton)).toHaveLength(1); - }); - - it('hides the add source engines button if the user does not have permissions', () => { - setMockValues({ - ...MOCK_VALUES, - myRole: { - canManageMetaEngineSourceEngines: false, - }, - }); - const wrapper = shallow(); - - expect(getPageHeaderActions(wrapper).find(AddSourceEnginesButton)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines.tsx deleted file mode 100644 index d2476faf4f3f5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiPanel } from '@elastic/eui'; - -import { AppLogic } from '../../app_logic'; -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { AddSourceEnginesButton, AddSourceEnginesModal, SourceEnginesTable } from './components'; -import { SOURCE_ENGINES_TITLE } from './i18n'; -import { SourceEnginesLogic } from './source_engines_logic'; - -export const SourceEngines: React.FC = () => { - const { - myRole: { canManageMetaEngineSourceEngines }, - } = useValues(AppLogic); - const { fetchIndexedEngines, fetchSourceEngines } = useActions(SourceEnginesLogic); - const { dataLoading, isModalOpen } = useValues(SourceEnginesLogic); - - useEffect(() => { - fetchIndexedEngines(); - fetchSourceEngines(); - }, []); - - return ( - ] : [], - }} - isLoading={dataLoading} - > - - - {isModalOpen && } - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts deleted file mode 100644 index b5bf839a0ae8f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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, - mockFlashMessageHelpers, - mockHttpValues, -} from '../../../__mocks__/kea_logic'; -import { mockRecursivelyFetchEngines } from '../../__mocks__'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { EngineLogic } from '../engine'; -import { EngineDetails } from '../engine/types'; - -import { SourceEnginesLogic } from './source_engines_logic'; - -describe('SourceEnginesLogic', () => { - const { http } = mockHttpValues; - const { mount } = new LogicMounter(SourceEnginesLogic); - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; - - const DEFAULT_VALUES = { - dataLoading: true, - modalLoading: false, - isModalOpen: false, - indexedEngines: [], - indexedEngineNames: [], - sourceEngines: [], - sourceEngineNames: [], - selectedEngineNamesToAdd: [], - selectableEngineNames: [], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('initializes with default values', () => { - mount(); - expect(SourceEnginesLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('closeModal', () => { - it('sets isModalOpen and modalLoading to false', () => { - mount({ - isModalOpen: true, - modalLoading: true, - }); - - SourceEnginesLogic.actions.closeModal(); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: false, - modalLoading: false, - }); - }); - }); - - describe('openModal', () => { - it('sets isModalOpen to true', () => { - mount({ - isModalOpen: false, - }); - - SourceEnginesLogic.actions.openModal(); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: true, - }); - }); - }); - - describe('onAddEnginesSelection', () => { - it('sets selectedEngineNamesToAdd to the specified value', () => { - mount(); - - SourceEnginesLogic.actions.onAddEnginesSelection(['source-engine-1', 'source-engine-2']); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - selectedEngineNamesToAdd: ['source-engine-1', 'source-engine-2'], - }); - }); - }); - - describe('setIndexedEngines', () => { - it('sets indexedEngines to the specified value', () => { - mount(); - - SourceEnginesLogic.actions.setIndexedEngines([ - { name: 'source-engine-1' }, - { name: 'source-engine-2', type: 'elasticsearch' }, - ] as EngineDetails[]); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - indexedEngines: [ - { name: 'source-engine-1' }, - { name: 'source-engine-2', type: 'elasticsearch' }, - ], - // Selectors - indexedEngineNames: ['source-engine-1', 'source-engine-2'], - selectableEngineNames: ['source-engine-1', 'source-engine-2'], - }); - }); - }); - - describe('onSourceEnginesFetch', () => { - it('sets sourceEngines to the specified value and dataLoading to false', () => { - mount(); - - SourceEnginesLogic.actions.onSourceEnginesFetch([ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[]); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: false, - sourceEngines: [{ name: 'source-engine-1' }, { name: 'source-engine-2' }], - // Selectors - sourceEngineNames: ['source-engine-1', 'source-engine-2'], - }); - }); - }); - - describe('onSourceEnginesAdd', () => { - it('adds to the existing sourceEngines', () => { - mount({ - sourceEngines: [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ] as EngineDetails[], - }); - - SourceEnginesLogic.actions.onSourceEnginesAdd([ - { name: 'source-engine-3' }, - { name: 'source-engine-4' }, - ] as EngineDetails[]); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - sourceEngines: [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - { name: 'source-engine-3' }, - { name: 'source-engine-4' }, - ], - // Selectors - sourceEngineNames: [ - 'source-engine-1', - 'source-engine-2', - 'source-engine-3', - 'source-engine-4', - ], - }); - }); - }); - - describe('onSourceEngineRemove', () => { - it('removes an item from the existing sourceEngines', () => { - mount({ - sourceEngines: [ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - { name: 'source-engine-3' }, - ] as EngineDetails[], - }); - - SourceEnginesLogic.actions.onSourceEngineRemove('source-engine-2'); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - sourceEngines: [{ name: 'source-engine-1' }, { name: 'source-engine-3' }], - // Selectors - sourceEngineNames: ['source-engine-1', 'source-engine-3'], - }); - }); - }); - }); - - describe('selectors', () => { - describe('indexedEngineNames', () => { - it('returns a flat array of `indexedEngine.name`s', () => { - mount({ - indexedEngines: [{ name: 'a' }, { name: 'b' }, { name: 'c' }], - }); - - expect(SourceEnginesLogic.values.indexedEngineNames).toEqual(['a', 'b', 'c']); - }); - }); - - describe('sourceEngineNames', () => { - it('returns a flat array of `sourceEngine.name`s', () => { - mount({ - sourceEngines: [{ name: 'd' }, { name: 'e' }], - }); - - expect(SourceEnginesLogic.values.sourceEngineNames).toEqual(['d', 'e']); - }); - }); - - describe('selectableEngineNames', () => { - it('returns a flat list of indexedEngineNames that are not already in sourceEngineNames', () => { - mount({ - indexedEngines: [{ name: 'a' }, { name: 'b' }, { name: 'c' }], - sourceEngines: [{ name: 'a' }, { name: 'b' }], - }); - - expect(SourceEnginesLogic.values.selectableEngineNames).toEqual(['c']); - }); - }); - }); - - describe('listeners', () => { - describe('fetchSourceEngines', () => { - it('calls onSourceEnginesFetch with all recursively fetched engines', () => { - jest.spyOn(SourceEnginesLogic.actions, 'onSourceEnginesFetch'); - - SourceEnginesLogic.actions.fetchSourceEngines(); - - expect(mockRecursivelyFetchEngines).toHaveBeenCalledWith( - expect.objectContaining({ - endpoint: '/internal/app_search/engines/some-engine/source_engines', - }) - ); - expect(SourceEnginesLogic.actions.onSourceEnginesFetch).toHaveBeenCalledWith([ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ]); - }); - }); - - describe('fetchIndexedEngines', () => { - it('calls setIndexedEngines with all recursively fetched engines', () => { - jest.spyOn(SourceEnginesLogic.actions, 'setIndexedEngines'); - - SourceEnginesLogic.actions.fetchIndexedEngines(); - - expect(mockRecursivelyFetchEngines).toHaveBeenCalledWith( - expect.objectContaining({ - endpoint: '/internal/app_search/engines', - query: { type: 'indexed' }, - }) - ); - expect(SourceEnginesLogic.actions.setIndexedEngines).toHaveBeenCalledWith([ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - ]); - }); - }); - - describe('addSourceEngines', () => { - it('sets modalLoading to true', () => { - mount({ modalLoading: false }); - - SourceEnginesLogic.actions.addSourceEngines([]); - - expect(SourceEnginesLogic.values).toEqual({ - ...DEFAULT_VALUES, - modalLoading: true, - }); - }); - - describe('on success', () => { - beforeEach(() => { - http.post.mockReturnValue(Promise.resolve()); - mount({ - indexedEngines: [{ name: 'source-engine-3' }, { name: 'source-engine-4' }], - }); - }); - - it('calls the bulk endpoint, adds source engines to state, and shows a success message', async () => { - jest.spyOn(SourceEnginesLogic.actions, 'onSourceEnginesAdd'); - - SourceEnginesLogic.actions.addSourceEngines(['source-engine-3', 'source-engine-4']); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/source_engines/bulk_create', - { - body: JSON.stringify({ source_engine_slugs: ['source-engine-3', 'source-engine-4'] }), - } - ); - expect(SourceEnginesLogic.actions.onSourceEnginesAdd).toHaveBeenCalledWith([ - { name: 'source-engine-3' }, - { name: 'source-engine-4' }, - ]); - expect(flashSuccessToast).toHaveBeenCalledWith( - '2 engines were added to this meta engine' - ); - }); - - it('re-initializes the engine and closes the modal', async () => { - jest.spyOn(EngineLogic.actions, 'initializeEngine'); - jest.spyOn(SourceEnginesLogic.actions, 'closeModal'); - - SourceEnginesLogic.actions.addSourceEngines([]); - await nextTick(); - - expect(EngineLogic.actions.initializeEngine).toHaveBeenCalled(); - expect(SourceEnginesLogic.actions.closeModal).toHaveBeenCalled(); - }); - }); - - describe('on error', () => { - beforeEach(() => { - http.post.mockReturnValue(Promise.reject()); - mount(); - }); - - it('flashes errors and closes the modal', async () => { - jest.spyOn(SourceEnginesLogic.actions, 'closeModal'); - - SourceEnginesLogic.actions.addSourceEngines([]); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - expect(SourceEnginesLogic.actions.closeModal).toHaveBeenCalled(); - }); - }); - }); - - describe('removeSourceEngine', () => { - describe('on success', () => { - beforeEach(() => { - http.delete.mockReturnValue(Promise.resolve()); - mount(); - }); - - it('calls the delete endpoint and removes source engines from state', async () => { - jest.spyOn(SourceEnginesLogic.actions, 'onSourceEngineRemove'); - - SourceEnginesLogic.actions.removeSourceEngine('source-engine-2'); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/source_engines/source-engine-2' - ); - expect(SourceEnginesLogic.actions.onSourceEngineRemove).toHaveBeenCalledWith( - 'source-engine-2' - ); - }); - - it('shows a success message', async () => { - SourceEnginesLogic.actions.removeSourceEngine('source-engine-2'); - await nextTick(); - - expect(flashSuccessToast).toHaveBeenCalledWith( - "Engine 'source-engine-2' was removed from this meta engine" - ); - }); - - it('re-initializes the engine', async () => { - jest.spyOn(EngineLogic.actions, 'initializeEngine'); - - SourceEnginesLogic.actions.removeSourceEngine('source-engine-2'); - await nextTick(); - - expect(EngineLogic.actions.initializeEngine).toHaveBeenCalledWith(); - }); - }); - - it('displays a flash message on error', async () => { - http.delete.mockReturnValueOnce(Promise.reject()); - mount(); - - SourceEnginesLogic.actions.removeSourceEngine('source-engine-2'); - await nextTick(); - - expect(flashAPIErrors).toHaveBeenCalledTimes(1); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.ts deleted file mode 100644 index 775ae55646b8e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.ts +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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 { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { recursivelyFetchEngines } from '../../utils/recursively_fetch_engines'; -import { EngineLogic } from '../engine'; -import { EngineDetails } from '../engine/types'; - -import { ADD_SOURCE_ENGINES_SUCCESS_MESSAGE, REMOVE_SOURCE_ENGINE_SUCCESS_MESSAGE } from './i18n'; - -export interface SourceEnginesLogicValues { - dataLoading: boolean; - modalLoading: boolean; - isModalOpen: boolean; - indexedEngines: EngineDetails[]; - indexedEngineNames: string[]; - sourceEngines: EngineDetails[]; - sourceEngineNames: string[]; - selectableEngineNames: string[]; - selectedEngineNamesToAdd: string[]; -} - -interface SourceEnginesLogicActions { - addSourceEngines: (sourceEngineNames: string[]) => { sourceEngineNames: string[] }; - fetchIndexedEngines: () => void; - fetchSourceEngines: () => void; - onSourceEngineRemove: (sourceEngineNameToRemove: string) => { sourceEngineNameToRemove: string }; - onSourceEnginesAdd: (sourceEnginesToAdd: EngineDetails[]) => { - sourceEnginesToAdd: EngineDetails[]; - }; - onSourceEnginesFetch: (sourceEngines: SourceEnginesLogicValues['sourceEngines']) => { - sourceEngines: SourceEnginesLogicValues['sourceEngines']; - }; - removeSourceEngine: (sourceEngineName: string) => { sourceEngineName: string }; - setIndexedEngines: (indexedEngines: EngineDetails[]) => { indexedEngines: EngineDetails[] }; - openModal: () => void; - closeModal: () => void; - onAddEnginesSelection: (selectedEngineNamesToAdd: string[]) => { - selectedEngineNamesToAdd: string[]; - }; -} - -export const SourceEnginesLogic = kea< - MakeLogicType ->({ - path: ['enterprise_search', 'app_search', 'source_engines_logic'], - actions: () => ({ - addSourceEngines: (sourceEngineNames) => ({ sourceEngineNames }), - fetchIndexedEngines: true, - fetchSourceEngines: true, - onSourceEngineRemove: (sourceEngineNameToRemove) => ({ sourceEngineNameToRemove }), - onSourceEnginesAdd: (sourceEnginesToAdd) => ({ sourceEnginesToAdd }), - onSourceEnginesFetch: (sourceEngines) => ({ sourceEngines }), - removeSourceEngine: (sourceEngineName) => ({ sourceEngineName }), - setIndexedEngines: (indexedEngines) => ({ indexedEngines }), - openModal: true, - closeModal: true, - onAddEnginesSelection: (selectedEngineNamesToAdd) => ({ selectedEngineNamesToAdd }), - }), - reducers: () => ({ - dataLoading: [ - true, - { - onSourceEnginesFetch: () => false, - }, - ], - modalLoading: [ - false, - { - addSourceEngines: () => true, - closeModal: () => false, - }, - ], - isModalOpen: [ - false, - { - openModal: () => true, - closeModal: () => false, - }, - ], - indexedEngines: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - setIndexedEngines: (_, { indexedEngines }) => indexedEngines, - }, - ], - selectedEngineNamesToAdd: [ - [], - { - closeModal: () => [], - // @ts-expect-error upgrade typescript v5.1.6 - onAddEnginesSelection: (_, { selectedEngineNamesToAdd }) => selectedEngineNamesToAdd, - }, - ], - sourceEngines: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onSourceEnginesAdd: (sourceEngines, { sourceEnginesToAdd }) => [ - ...sourceEngines, - ...sourceEnginesToAdd, - ], - // @ts-expect-error upgrade typescript v5.1.6 - onSourceEnginesFetch: (_, { sourceEngines }) => sourceEngines, - // @ts-expect-error upgrade typescript v5.1.6 - onSourceEngineRemove: (sourceEngines, { sourceEngineNameToRemove }) => - // @ts-expect-error upgrade typescript v5.1.6 - sourceEngines.filter((sourceEngine) => sourceEngine.name !== sourceEngineNameToRemove), - }, - ], - }), - selectors: { - indexedEngineNames: [ - (selectors) => [selectors.indexedEngines], - (indexedEngines) => indexedEngines.map((engine: EngineDetails) => engine.name), - ], - sourceEngineNames: [ - (selectors) => [selectors.sourceEngines], - (sourceEngines) => sourceEngines.map((engine: EngineDetails) => engine.name), - ], - selectableEngineNames: [ - (selectors) => [selectors.indexedEngineNames, selectors.sourceEngineNames], - (indexedEngineNames, sourceEngineNames) => - indexedEngineNames.filter((engineName: string) => !sourceEngineNames.includes(engineName)), - ], - }, - listeners: ({ actions, values }) => ({ - addSourceEngines: async ({ sourceEngineNames }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - await http.post(`/internal/app_search/engines/${engineName}/source_engines/bulk_create`, { - body: JSON.stringify({ - source_engine_slugs: sourceEngineNames, - }), - }); - - const sourceEnginesToAdd = values.indexedEngines.filter(({ name }) => - sourceEngineNames.includes(name) - ); - - actions.onSourceEnginesAdd(sourceEnginesToAdd); - flashSuccessToast(ADD_SOURCE_ENGINES_SUCCESS_MESSAGE(sourceEngineNames)); - EngineLogic.actions.initializeEngine(); - } catch (e) { - flashAPIErrors(e); - } finally { - actions.closeModal(); - } - }, - fetchSourceEngines: () => { - const { engineName } = EngineLogic.values; - - recursivelyFetchEngines({ - endpoint: `/internal/app_search/engines/${engineName}/source_engines`, - onComplete: (engines) => actions.onSourceEnginesFetch(engines), - }); - }, - fetchIndexedEngines: () => { - recursivelyFetchEngines({ - endpoint: '/internal/app_search/engines', - onComplete: (engines) => actions.setIndexedEngines(engines), - query: { type: 'indexed' }, - }); - }, - removeSourceEngine: async ({ sourceEngineName }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - await http.delete( - `/internal/app_search/engines/${engineName}/source_engines/${sourceEngineName}` - ); - - actions.onSourceEngineRemove(sourceEngineName); - flashSuccessToast(REMOVE_SOURCE_ENGINE_SUCCESS_MESSAGE(sourceEngineName)); - - // Changing source engines can change schema conflicts and invalid boosts, - // so we re-initialize the engine to re-fetch that data - EngineLogic.actions.initializeEngine(); // - } catch (e) { - flashAPIErrors(e); - } - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx deleted file mode 100644 index cb059a5b86e7d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { EmptyState, SynonymModal } from '.'; - -describe('EmptyState', () => { - it('renders', () => { - const wrapper = shallow() - .find(EuiEmptyPrompt) - .dive(); - - expect(wrapper.find('h2').text()).toEqual('Create your first synonym set'); - expect(wrapper.find(EuiButton).prop('href')).toEqual( - expect.stringContaining(docLinks.appSearchSynonyms) - ); - }); - - it('renders the add synonym modal', () => { - const wrapper = shallow(); - - expect(wrapper.find(SynonymModal)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx deleted file mode 100644 index ff7e8ce16c6d2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { docLinks } from '../../../../shared/doc_links'; - -import { SynonymModal, SynonymIcon } from '.'; - -export const EmptyState: React.FC = () => { - return ( - <> - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.title', { - defaultMessage: 'Create your first synonym set', - })} - - } - body={ -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.description', { - defaultMessage: - 'Synonyms relate queries with similar context or meaning together. Use them to guide users to relevant content.', - })} -

    - } - actions={ - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel', { - defaultMessage: 'Read the synonyms guide', - })} - - } - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/index.ts deleted file mode 100644 index 56fcb2ff163e9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 { SynonymIcon } from './synonym_icon'; -export { SynonymCard } from './synonym_card'; -export { SynonymModal } from './synonym_modal'; -export { EmptyState } from './empty_state'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.test.tsx deleted file mode 100644 index a617c9286cc2a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCard, EuiButton } from '@elastic/eui'; - -import { SynonymCard, SynonymIcon } from '.'; - -describe('SynonymCard', () => { - const MOCK_SYNONYM_SET = { - id: 'syn-1234567890', - synonyms: ['lorem', 'ipsum', 'dolor', 'sit', 'amet'], - }; - const actions = { - openModal: jest.fn(), - }; - - setMockActions(actions); - const wrapper = shallow() - .find(EuiCard) - .dive(); - - it('renders with the first synonym as the title', () => { - expect(wrapper.find('h2').text()).toEqual('lorem'); - }); - - it('renders a synonym icon for each subsequent synonym', () => { - expect(wrapper.find(SynonymIcon)).toHaveLength(4); - }); - - it('renders a manage synonym button', () => { - wrapper.find(EuiButton).simulate('click'); - expect(actions.openModal).toHaveBeenCalledWith(MOCK_SYNONYM_SET); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.tsx deleted file mode 100644 index 28f6a251240cc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_card.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 } from 'kea'; - -import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiText, EuiButton } from '@elastic/eui'; - -import { SynonymsLogic } from '..'; -import { MANAGE_BUTTON_LABEL } from '../../../../shared/constants'; - -import { SynonymSet } from '../types'; - -import { SynonymIcon } from '.'; - -export const SynonymCard: React.FC = (synonymSet) => { - const { openModal } = useActions(SynonymsLogic); - - const [firstSynonym, ...remainingSynonyms] = synonymSet.synonyms; - - return ( - - - openModal(synonymSet)}>{MANAGE_BUTTON_LABEL} - - - } - > - - {remainingSynonyms.map((synonym) => ( -
    - {synonym} -
    - ))} -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.test.tsx deleted file mode 100644 index 9418c3f396c5e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { SynonymIcon } from '.'; - -describe('SynonymIcon', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.hasClass('euiIcon')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.tsx deleted file mode 100644 index f76b8be818c47..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_icon.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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'; - -export const SynonymIcon: React.FC = ({ ...props }) => ( - - - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_modal.test.tsx deleted file mode 100644 index 074cfa6fce9bb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_modal.test.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiModal } from '@elastic/eui'; - -import { MultiInputRows } from '../../multi_input_rows'; - -import { SynonymModal } from '.'; - -describe('SynonymModal', () => { - const MOCK_SYNONYM_SET = { - id: 'syn-1234567890', - synonyms: ['a', 'b', 'c'], - }; - const values = { - isModalOpen: true, - modalLoading: false, - }; - const actions = { - closeModal: jest.fn(), - createSynonymSet: jest.fn(), - updateSynonymSet: jest.fn(), - deleteSynonymSet: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders a modal', () => { - const wrapper = shallow(); - - wrapper.find(EuiModal).simulate('close'); - expect(actions.closeModal).toHaveBeenCalled(); - }); - - it('renders a form submit button with a loading state', () => { - setMockValues({ ...values, modalLoading: true }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="submitSynonymSetButton"]').prop('isLoading')).toBe(true); - }); - - describe('new synonym set', () => { - setMockActions(actions); - setMockValues({ ...values, activeSynonymSet: null }); - const wrapper = shallow(); - - it('renders', () => { - expect(wrapper.find('EuiModalHeaderTitle').render().text()).toEqual('Add a synonym set'); - }); - - it('populates MultiInputRows with two empty rows', () => { - expect(wrapper.find(MultiInputRows).prop('id')).toEqual('createNewSynonymSet'); - expect(wrapper.find(MultiInputRows).prop('initialValues')).toEqual(['', '']); - }); - - it('calls createSynonymSet on submit', () => { - wrapper.find(MultiInputRows).simulate('submit', ['new', 'synonyms']); - - expect(actions.createSynonymSet).toHaveBeenCalledWith(['new', 'synonyms']); - }); - - it('does not render a delete button', () => { - expect(wrapper.find('[data-test-subj="deleteSynonymSetButton"]')).toHaveLength(0); - }); - }); - - describe('existing synonym set', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - setMockValues({ ...values, activeSynonymSet: MOCK_SYNONYM_SET }); - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.find('EuiModalHeaderTitle').render().text()).toEqual('Manage synonym set'); - }); - - it('populates MultiInputRows with ID & initial values', () => { - expect(wrapper.find(MultiInputRows).prop('id')).toEqual('syn-1234567890'); - expect(wrapper.find(MultiInputRows).prop('initialValues')).toEqual(['a', 'b', 'c']); - }); - - it('calls updateSynonymSet on submit', () => { - wrapper.find(MultiInputRows).simulate('submit', ['updated', 'synonyms']); - - expect(actions.updateSynonymSet).toHaveBeenCalledWith({ - id: 'syn-1234567890', - synonyms: ['updated', 'synonyms'], - }); - }); - - it('renders a delete button', () => { - const confirmSpy = jest.spyOn(global, 'confirm'); - const deleteButton = wrapper.find('[data-test-subj="deleteSynonymSetButton"]'); - expect(deleteButton).toHaveLength(1); - - confirmSpy.mockReturnValueOnce(false); - deleteButton.simulate('click'); - expect(actions.deleteSynonymSet).not.toHaveBeenCalled(); - - confirmSpy.mockReturnValueOnce(true); - deleteButton.simulate('click'); - expect(actions.deleteSynonymSet).toHaveBeenCalledWith('syn-1234567890'); - }); - }); - - it('does not render if the modal is not open', () => { - setMockValues({ ...values, isModalOpen: false }); - const wrapper = shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_modal.tsx deleted file mode 100644 index ed3274112db9e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/synonym_modal.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 { useValues, useActions } from 'kea'; - -import { - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiButtonEmpty, - useGeneratedHtmlId, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { SynonymsLogic } from '..'; -import { - CANCEL_BUTTON_LABEL, - DELETE_BUTTON_LABEL, - SAVE_BUTTON_LABEL, -} from '../../../../shared/constants'; -import { FlashMessages } from '../../../../shared/flash_messages'; -import { MultiInputRows } from '../../multi_input_rows'; - -import { SYNONYM_CREATE_TITLE, SYNONYM_UPDATE_TITLE, DELETE_CONFIRMATION } from '../constants'; - -export const SynonymModal: React.FC = () => { - const { isModalOpen, modalLoading, activeSynonymSet } = useValues(SynonymsLogic); - const { closeModal, createSynonymSet, updateSynonymSet, deleteSynonymSet } = - useActions(SynonymsLogic); - const modalTitleId = useGeneratedHtmlId(); - - const modalTitle = activeSynonymSet ? SYNONYM_UPDATE_TITLE : SYNONYM_CREATE_TITLE; - const id = activeSynonymSet?.id || 'createNewSynonymSet'; - const synonyms = activeSynonymSet?.synonyms || ['', '']; - const onSubmit = activeSynonymSet - ? (updatedSynonyms: string[]) => updateSynonymSet({ id, synonyms: updatedSynonyms }) - : (newSynonyms: string[]) => createSynonymSet(newSynonyms); - - return isModalOpen ? ( - - - {modalTitle} - - - - - - - - - {activeSynonymSet && ( - - { - if (window.confirm(DELETE_CONFIRMATION)) deleteSynonymSet(id); - }} - data-test-subj="deleteSynonymSetButton" - > - {DELETE_BUTTON_LABEL} - - - )} - - - - {CANCEL_BUTTON_LABEL} - - - - - {SAVE_BUTTON_LABEL} - - - - - - ) : null; -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts deleted file mode 100644 index d34ffdea7c2ce..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/constants.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { DEFAULT_META } from '../../../shared/constants'; - -export const SYNONYMS_PAGE_META = { - page: { - ...DEFAULT_META.page, - size: 12, // Use a multiple of 3, since synonym cards are in rows of 3 - }, -}; - -export const SYNONYMS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.title', - { defaultMessage: 'Synonyms' } -); -export const SYNONYM_CREATE_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetTitle', - { defaultMessage: 'Add a synonym set' } -); -export const SYNONYM_UPDATE_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.updateSynonymSetTitle', - { defaultMessage: 'Manage synonym set' } -); - -export const CREATE_SUCCESS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.createSuccessMessage', - { defaultMessage: 'Synonym set created' } -); -export const UPDATE_SUCCESS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.updateSuccessMessage', - { defaultMessage: 'Synonym set updated' } -); -export const DELETE_CONFIRMATION = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.deleteConfirmationMessage', - { defaultMessage: 'Are you sure you want to delete this synonym set?' } -); -export const DELETE_SUCCESS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.deleteSuccessMessage', - { defaultMessage: 'Synonym set deleted' } -); -export const SYNONYM_IMPACT_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.impactDescription', - { defaultMessage: 'The set will impact your results shortly.' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts deleted file mode 100644 index 4b9de7ef90603..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { SYNONYMS_TITLE } from './constants'; -export { Synonyms } from './synonyms'; -export { SynonymsLogic } from './synonyms_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx deleted file mode 100644 index 11a04825e2f20..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; -import '../../../__mocks__/shallow_useeffect.mock'; -import '../../__mocks__/engine_logic.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton, EuiPagination } from '@elastic/eui'; - -import { rerender, getPageHeaderActions } from '../../../test_helpers'; - -import { SynonymCard, SynonymModal } from './components'; - -import { Synonyms } from '.'; - -describe('Synonyms', () => { - const MOCK_SYNONYM_SET = { - id: 'syn-1234567890', - synonyms: ['a', 'b', 'c'], - }; - - const values = { - synonymSets: [MOCK_SYNONYM_SET, MOCK_SYNONYM_SET, MOCK_SYNONYM_SET], - meta: { page: { current: 1 } }, - dataLoading: false, - }; - const actions = { - loadSynonyms: jest.fn(), - onPaginate: jest.fn(), - openModal: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find(SynonymCard)).toHaveLength(3); - expect(wrapper.find(SynonymModal)).toHaveLength(1); - }); - - it('renders a create action button', () => { - const wrapper = shallow(); - getPageHeaderActions(wrapper).find(EuiButton).simulate('click'); - expect(actions.openModal).toHaveBeenCalled(); - }); - - describe('loading', () => { - it('renders a loading state on initial page load', () => { - setMockValues({ ...values, synonymSets: [], dataLoading: true }); - const wrapper = shallow(); - - expect(wrapper.prop('isLoading')).toEqual(true); - }); - - it('does not render a full loading state after initial page load', () => { - setMockValues({ ...values, synonymSets: [MOCK_SYNONYM_SET], dataLoading: true }); - const wrapper = shallow(); - - expect(wrapper.prop('isLoading')).toEqual(false); - }); - }); - - describe('API & pagination', () => { - it('loads synonyms on page load and on pagination', () => { - const wrapper = shallow(); - expect(actions.loadSynonyms).toHaveBeenCalledTimes(1); - - setMockValues({ ...values, meta: { page: { current: 5 } } }); - rerender(wrapper); - expect(actions.loadSynonyms).toHaveBeenCalledTimes(2); - }); - - it('automatically paginations users back a page if they delete the only remaining synonym on the page', () => { - setMockValues({ ...values, meta: { page: { current: 5 } }, synonymSets: [] }); - shallow(); - - expect(actions.onPaginate).toHaveBeenCalledWith(4); - }); - - it('does not paginate backwards if the user is on the first page (should show the state instead)', () => { - setMockValues({ ...values, meta: { page: { current: 1 } }, synonymSets: [] }); - const wrapper = shallow(); - - expect(actions.onPaginate).not.toHaveBeenCalled(); - expect(wrapper.prop('isEmptyState')).toEqual(true); - }); - - it('handles off-by-one shenanigans between EuiPagination and our API', () => { - setMockValues({ - ...values, - meta: { page: { total_pages: 10, current: 1 } }, - }); - const wrapper = shallow(); - const pagination = wrapper.find(EuiPagination); - - expect(pagination.prop('pageCount')).toEqual(10); - expect(pagination.prop('activePage')).toEqual(0); - - pagination.simulate('pageClick', 4); - expect(actions.onPaginate).toHaveBeenCalledWith(5); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx deleted file mode 100644 index 14108f11487b6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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, { useEffect } from 'react'; - -import { useValues, useActions } from 'kea'; - -import { EuiButton, EuiSpacer, EuiFlexGrid, EuiFlexItem, EuiPagination } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { getEngineBreadcrumbs } from '../engine'; -import { AppSearchPageTemplate } from '../layout'; - -import { SynonymCard, SynonymModal, EmptyState } from './components'; -import { SYNONYMS_TITLE } from './constants'; - -import { SynonymsLogic } from '.'; - -export const Synonyms: React.FC = () => { - const { loadSynonyms, onPaginate, openModal } = useActions(SynonymsLogic); - const { synonymSets, meta, dataLoading } = useValues(SynonymsLogic); - const hasSynonyms = synonymSets.length > 0; - - useEffect(() => { - loadSynonyms(); - }, [meta.page.current]); - - useEffect(() => { - // If users delete the only synonym set on the page, send them back to the previous page - if (!hasSynonyms && meta.page.current !== 1) { - onPaginate(meta.page.current - 1); - } - }, [synonymSets]); - - return ( - openModal(null)}> - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.synonyms.createSynonymSetButtonLabel', - { defaultMessage: 'Create a synonym set' } - )} - , - ], - }} - isLoading={dataLoading && !hasSynonyms} - isEmptyState={!hasSynonyms} - emptyState={} - > - - {synonymSets.map(({ id, synonyms }) => ( - - - - ))} - - - onPaginate(pageIndex + 1)} - /> - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts deleted file mode 100644 index 92f07030e76b9..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts +++ /dev/null @@ -1,318 +0,0 @@ -/* - * 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, - mockHttpValues, - mockFlashMessageHelpers, -} from '../../../__mocks__/kea_logic'; -import '../../__mocks__/engine_logic.mock'; - -import { nextTick } from '@kbn/test-jest-helpers'; - -import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { SYNONYMS_PAGE_META } from './constants'; - -import { SynonymsLogic } from '.'; - -describe('SynonymsLogic', () => { - const { mount } = new LogicMounter(SynonymsLogic); - const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast, clearFlashMessages } = mockFlashMessageHelpers; - - const MOCK_SYNONYM_SET = { - id: 'some-synonym-id', - synonyms: ['hello', 'world'], - }; - const MOCK_SYNONYMS_RESPONSE = { - meta: { - page: { - current: 1, - size: 12, - total_results: 1, - total_pages: 1, - }, - }, - results: [MOCK_SYNONYM_SET], - }; - - const DEFAULT_VALUES = { - dataLoading: true, - synonymSets: [], - meta: SYNONYMS_PAGE_META, - isModalOpen: false, - activeSynonymSet: null, - modalLoading: false, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('has expected default values', () => { - mount(); - expect(SynonymsLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('actions', () => { - describe('onSynonymsLoad', () => { - it('should set synonyms and meta state, & dataLoading to false', () => { - mount(); - - SynonymsLogic.actions.onSynonymsLoad(MOCK_SYNONYMS_RESPONSE); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - synonymSets: MOCK_SYNONYMS_RESPONSE.results, - meta: MOCK_SYNONYMS_RESPONSE.meta, - dataLoading: false, - }); - }); - }); - - describe('onPaginate', () => { - it('should set meta.page.current state', () => { - mount(); - - SynonymsLogic.actions.onPaginate(3); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - meta: { page: { ...DEFAULT_VALUES.meta.page, current: 3 } }, - }); - }); - }); - - describe('openModal', () => { - it('should set isModalOpen to true and populate an activeSynonymSet', () => { - mount({ isModalOpen: false, activeSynonymSet: null }); - - SynonymsLogic.actions.openModal(MOCK_SYNONYM_SET); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: true, - activeSynonymSet: MOCK_SYNONYM_SET, - }); - }); - - describe('closeModal', () => { - it('should set isModalOpen & modalLoading to false and reset activeSynonymSet', () => { - mount({ isModalOpen: true, modalLoading: true, activeSynonymSet: MOCK_SYNONYM_SET }); - - SynonymsLogic.actions.closeModal(); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - isModalOpen: false, - modalLoading: false, - activeSynonymSet: null, - }); - }); - }); - }); - }); - - describe('listeners', () => { - describe('loadSynonyms', () => { - it('should set dataLoading state', () => { - mount({ dataLoading: false }); - - SynonymsLogic.actions.loadSynonyms(); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - dataLoading: true, - }); - }); - - it('should make an API call and set synonyms & meta state', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_SYNONYMS_RESPONSE)); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymsLoad'); - - SynonymsLogic.actions.loadSynonyms(); - await nextTick(); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/synonyms', { - query: { - 'page[current]': 1, - 'page[size]': 12, - }, - }); - expect(SynonymsLogic.actions.onSynonymsLoad).toHaveBeenCalledWith(MOCK_SYNONYMS_RESPONSE); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - mount(); - SynonymsLogic.actions.loadSynonyms(); - }); - }); - - describe('createSynonymSet', () => { - it('should set modalLoading state and clear flash messages', () => { - mount({ modalLoading: false }); - - SynonymsLogic.actions.createSynonymSet(['test']); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - modalLoading: true, - }); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('should make a POST API call', async () => { - http.post.mockReturnValueOnce(Promise.resolve()); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymSetSuccess'); - - SynonymsLogic.actions.createSynonymSet(['a', 'b', 'c']); - await nextTick(); - - expect(http.post).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/synonyms', - { - body: '{"synonyms":["a","b","c"]}', - } - ); - expect(SynonymsLogic.actions.onSynonymSetSuccess).toHaveBeenCalledWith( - 'Synonym set created' - ); - }); - - it('handles errors', async () => { - http.post.mockReturnValueOnce(Promise.reject('error')); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymSetError'); - - SynonymsLogic.actions.createSynonymSet([]); - await nextTick(); - - expect(SynonymsLogic.actions.onSynonymSetError).toHaveBeenCalled(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - - describe('updateSynonymSet', () => { - it('should set modalLoading state and clear flash messages', () => { - mount({ modalLoading: false }); - - SynonymsLogic.actions.updateSynonymSet(MOCK_SYNONYM_SET); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - modalLoading: true, - }); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('should make a PUT API call', async () => { - http.put.mockReturnValueOnce(Promise.resolve()); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymSetSuccess'); - - SynonymsLogic.actions.updateSynonymSet(MOCK_SYNONYM_SET); - await nextTick(); - - expect(http.put).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/synonyms/some-synonym-id', - { - body: '{"synonyms":["hello","world"]}', - } - ); - expect(SynonymsLogic.actions.onSynonymSetSuccess).toHaveBeenCalledWith( - 'Synonym set updated' - ); - }); - - it('handles errors', async () => { - http.put.mockReturnValueOnce(Promise.reject('error')); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymSetError'); - - SynonymsLogic.actions.updateSynonymSet(MOCK_SYNONYM_SET); - await nextTick(); - - expect(SynonymsLogic.actions.onSynonymSetError).toHaveBeenCalled(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - - describe('deleteSynonymSet', () => { - it('should set modalLoading state and clear flash messages', () => { - mount({ modalLoading: false }); - - SynonymsLogic.actions.deleteSynonymSet('id'); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - modalLoading: true, - }); - expect(clearFlashMessages).toHaveBeenCalled(); - }); - - it('should make a DELETE API call', async () => { - http.delete.mockReturnValueOnce(Promise.resolve()); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymSetSuccess'); - - SynonymsLogic.actions.deleteSynonymSet('some-synonym-id'); - await nextTick(); - - expect(http.delete).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/synonyms/some-synonym-id' - ); - expect(SynonymsLogic.actions.onSynonymSetSuccess).toHaveBeenCalledWith( - 'Synonym set deleted' - ); - }); - - it('handles errors', async () => { - http.delete.mockReturnValueOnce(Promise.reject('error')); - mount(); - jest.spyOn(SynonymsLogic.actions, 'onSynonymSetError'); - - SynonymsLogic.actions.deleteSynonymSet('id'); - await nextTick(); - - expect(SynonymsLogic.actions.onSynonymSetError).toHaveBeenCalled(); - expect(flashAPIErrors).toHaveBeenCalledWith('error'); - }); - }); - - describe('onSynonymSetSuccess', () => { - it('should reload synonyms, close the modal, and flash a success toast', async () => { - mount(); - jest.spyOn(SynonymsLogic.actions, 'loadSynonyms'); - jest.spyOn(SynonymsLogic.actions, 'closeModal'); - - await SynonymsLogic.actions.onSynonymSetSuccess('Success!!'); - - expect(SynonymsLogic.actions.loadSynonyms).toHaveBeenCalled(); - expect(SynonymsLogic.actions.closeModal).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalledWith('Success!!', { - text: 'The set will impact your results shortly.', - }); - }); - }); - - describe('onSynonymSetError', () => { - it('should set modalLoading to false', () => { - mount({ modalLoading: true }); - - SynonymsLogic.actions.onSynonymSetError(); - - expect(SynonymsLogic.values).toEqual({ - ...DEFAULT_VALUES, - modalLoading: false, - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts deleted file mode 100644 index 68f823436d18b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.ts +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; -import { - clearFlashMessages, - flashAPIErrors, - flashSuccessToast, -} from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; -import { updateMetaPageIndex } from '../../../shared/table_pagination'; -import { EngineLogic } from '../engine'; - -import { - SYNONYMS_PAGE_META, - CREATE_SUCCESS, - UPDATE_SUCCESS, - DELETE_SUCCESS, - SYNONYM_IMPACT_MESSAGE, -} from './constants'; -import { SynonymSet, SynonymsApiResponse } from './types'; - -interface SynonymsValues { - dataLoading: boolean; - synonymSets: SynonymSet[]; - meta: Meta; - isModalOpen: boolean; - activeSynonymSet: SynonymSet | null; - modalLoading: boolean; -} - -interface SynonymsActions { - loadSynonyms(): void; - onSynonymsLoad(response: SynonymsApiResponse): SynonymsApiResponse; - onPaginate(newPageIndex: number): { newPageIndex: number }; - openModal(synonymSet: SynonymSet | null): { synonymSet: SynonymSet | null }; - closeModal(): void; - createSynonymSet(synonyms: SynonymSet['synonyms']): { synonyms: SynonymSet['synonyms'] }; - updateSynonymSet(synonymSet: SynonymSet): SynonymSet; - deleteSynonymSet(id: SynonymSet['id']): { id: SynonymSet['id'] }; - onSynonymSetSuccess(successMessage: string): { successMessage: string }; - onSynonymSetError(): void; -} - -export const SynonymsLogic = kea>({ - path: ['enterprise_search', 'app_search', 'synonyms_logic'], - actions: () => ({ - loadSynonyms: true, - onSynonymsLoad: ({ results, meta }) => ({ results, meta }), - onPaginate: (newPageIndex) => ({ newPageIndex }), - openModal: (synonymSet) => ({ synonymSet }), - closeModal: true, - createSynonymSet: (synonyms) => ({ synonyms }), - updateSynonymSet: ({ id, synonyms }) => ({ id, synonyms }), - deleteSynonymSet: (id) => ({ id }), - onSynonymSetSuccess: (successMessage) => ({ successMessage }), - onSynonymSetError: true, - }), - reducers: () => ({ - dataLoading: [ - true, - { - loadSynonyms: () => true, - onSynonymsLoad: () => false, - }, - ], - synonymSets: [ - [], - { - // @ts-expect-error upgrade typescript v5.1.6 - onSynonymsLoad: (_, { results }) => results, - }, - ], - meta: [ - SYNONYMS_PAGE_META, - { - // @ts-expect-error upgrade typescript v5.1.6 - onSynonymsLoad: (_, { meta }) => meta, - // @ts-expect-error upgrade typescript v5.1.6 - onPaginate: (state, { newPageIndex }) => updateMetaPageIndex(state, newPageIndex), - }, - ], - isModalOpen: [ - false, - { - openModal: () => true, - closeModal: () => false, - }, - ], - activeSynonymSet: [ - null, - { - // @ts-expect-error upgrade typescript v5.1.6 - openModal: (_, { synonymSet }) => synonymSet, - closeModal: () => null, - }, - ], - modalLoading: [ - false, - { - createSynonymSet: () => true, - updateSynonymSet: () => true, - deleteSynonymSet: () => true, - onSynonymSetError: () => false, - closeModal: () => false, - }, - ], - }), - listeners: ({ actions, values }) => ({ - loadSynonyms: async () => { - const { meta } = values; - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - - try { - const response = await http.get( - `/internal/app_search/engines/${engineName}/synonyms`, - { - query: { - 'page[current]': meta.page.current, - 'page[size]': meta.page.size, - }, - } - ); - actions.onSynonymsLoad(response); - } catch (e) { - flashAPIErrors(e); - } - }, - createSynonymSet: async ({ synonyms }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - clearFlashMessages(); - - try { - await http.post(`/internal/app_search/engines/${engineName}/synonyms`, { - body: JSON.stringify({ synonyms }), - }); - actions.onSynonymSetSuccess(CREATE_SUCCESS); - } catch (e) { - actions.onSynonymSetError(); - flashAPIErrors(e); - } - }, - updateSynonymSet: async ({ id, synonyms }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - clearFlashMessages(); - - try { - await http.put(`/internal/app_search/engines/${engineName}/synonyms/${id}`, { - body: JSON.stringify({ synonyms }), - }); - actions.onSynonymSetSuccess(UPDATE_SUCCESS); - } catch (e) { - actions.onSynonymSetError(); - flashAPIErrors(e); - } - }, - deleteSynonymSet: async ({ id }) => { - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - clearFlashMessages(); - - try { - await http.delete(`/internal/app_search/engines/${engineName}/synonyms/${id}`); - actions.onSynonymSetSuccess(DELETE_SUCCESS); - } catch (e) { - actions.onSynonymSetError(); - flashAPIErrors(e); - } - }, - onSynonymSetSuccess: async ({ successMessage }) => { - await actions.loadSynonyms(); - actions.closeModal(); - flashSuccessToast(successMessage, { text: SYNONYM_IMPACT_MESSAGE }); - }, - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/types.ts deleted file mode 100644 index 2f6da766a6d50..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 { Meta } from '../../../../../common/types'; - -export interface SynonymSet { - id: string; - synonyms: string[]; -} - -export interface SynonymsApiResponse { - results: SynonymSet[]; - meta: Meta; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/constants.ts deleted file mode 100644 index bcf5c64178e2a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/constants.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -// This is the value used for an engine that has no explicit 'language' set, it works -// with all languages. -export const UNIVERSAL_LANGUAGE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.universalLanguage', - { - defaultMessage: 'Universal', - } -); - -export const RESTORE_DEFAULTS_BUTTON_LABEL = i18n.translate( - 'xpack.enterpriseSearch.appSearch.actions.restoreDefaultsButonLabel', - { - defaultMessage: 'Restore defaults', - } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js deleted file mode 100644 index a59ca6654442e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress.config.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 { defineCypressConfig } from '@kbn/cypress-config'; - -export default defineCypressConfig({ - defaultCommandTimeout: 120000, - e2e: { - baseUrl: 'http://localhost:5601', - supportFile: './cypress/support/commands.ts', - }, - env: { - password: 'changeme', - username: 'elastic', - }, - execTimeout: 120000, - pageLoadTimeout: 180000, - retries: { - runMode: 2, - }, - screenshotsFolder: '../../../target/cypress/screenshots', - video: false, - videosFolder: '../../../target/cypress/videos', - viewportHeight: 1200, - viewportWidth: 1600, -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/e2e/engines.cy.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/e2e/engines.cy.ts deleted file mode 100644 index c57518a55cb1a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/e2e/engines.cy.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { login, checkA11y } from '../support/commands'; - -context('Engines', () => { - beforeEach(() => { - login(); - }); - - it('renders', () => { - cy.contains('Engines'); - checkA11y(); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/support/commands.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/support/commands.ts deleted file mode 100644 index 9c60d044aa21a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/support/commands.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { checkA11y } from '../../../shared/cypress/commands'; -import { login as baseLogin } from '../../../shared/cypress/commands'; -import { appSearchPath } from '../../../shared/cypress/routes'; - -interface Login { - path?: string; - username?: string; - password?: string; -} -export const login = ({ path = '/', ...args }: Login = {}) => { - baseLogin({ ...args }); - cy.visit(`${appSearchPath}${path}`); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/tsconfig.json b/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/tsconfig.json deleted file mode 100644 index 4efdc9033ae2c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/cypress/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../shared/cypress/tsconfig.json", - "compilerOptions": { "outDir": "target/types" }, - "include": ["./**/*"], - "exclude": [ - "target/**/*", - ], - "kbn_references": [ - { "path": "../../shared/cypress/tsconfig.json" }, - // cypress projects that are nested inside of other ts project use code - // from the parent ts project in ways that can't be automatically deteceted - // at this time so we have to force the inclusion of this reference - { - "path": "../../../../tsconfig.json", - "force": true - }, - ], -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/cursor_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/icons/cursor_icon.tsx deleted file mode 100644 index c874f135563ec..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/cursor_icon.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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'; - -// TODO: This icon will be added to EUI soon - we should remove this custom SVG when once it's available in EUI -export const CursorIcon: React.FC = ({ ...props }) => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/engine_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/icons/engine_icon.tsx deleted file mode 100644 index 13c948787c77f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/engine_icon.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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'; - -export const EngineIcon: React.FC = ({ ...props }) => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/icons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/icons/icons.test.tsx deleted file mode 100644 index b3fc5b4eb16cb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/icons.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { CursorIcon } from './cursor_icon'; -import { EngineIcon } from './engine_icon'; -import { MetaEngineIcon } from './meta_engine_icon'; - -describe('shared App Search icons', () => { - it('renders a cursor icon', () => { - const wrapper = shallow(); - expect(wrapper.hasClass('euiIcon')).toBe(true); - }); - - it('renders an engine icon', () => { - const wrapper = shallow(); - expect(wrapper.hasClass('euiIcon')).toBe(true); - }); - - it('renders a meta engine icon', () => { - const wrapper = shallow(); - expect(wrapper.hasClass('euiIcon')).toBe(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/icons/index.ts deleted file mode 100644 index 1669fdab5ec4e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 { CursorIcon } from './cursor_icon'; -export { EngineIcon } from './engine_icon'; -export { MetaEngineIcon } from './meta_engine_icon'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/meta_engine_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/icons/meta_engine_icon.tsx deleted file mode 100644 index 94fb2cd37c0cb..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/icons/meta_engine_icon.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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'; - -export const MetaEngineIcon: React.FC = ({ ...props }) => ( - -); 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 deleted file mode 100644 index 2c73e7606cd86..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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 { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__'; -import { setMockValues } from '../__mocks__/kea_logic'; -import '../__mocks__/shallow_useeffect.mock'; -import '../__mocks__/enterprise_search_url.mock'; - -import React from 'react'; - -import { Redirect } from 'react-router-dom'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { rerender } from '../test_helpers'; - -jest.mock('./app_logic', () => ({ AppLogic: jest.fn() })); -import { AppLogic } from './app_logic'; - -import { Credentials } from './components/credentials'; -import { EngineRouter } from './components/engine'; -import { EngineCreation } from './components/engine_creation'; -import { EnginesOverview } from './components/engines'; -import { Library } from './components/library'; -import { MetaEngineCreation } from './components/meta_engine_creation'; -import { RoleMappings } from './components/role_mappings'; -import { Settings } from './components/settings'; -import { SetupGuide } from './components/setup_guide'; - -import { AppSearch, AppSearchUnconfigured, AppSearchConfigured } from '.'; - -describe('AppSearch', () => { - it('always renders the Setup Guide', () => { - const wrapper = shallow(); - - expect(wrapper.find(SetupGuide)).toHaveLength(1); - }); - - it('renders AppSearchUnconfigured when config.host is not set', () => { - setMockValues({ config: { host: '' } }); - const wrapper = shallow(); - - expect(wrapper.find(AppSearchUnconfigured)).toHaveLength(1); - }); - - it('renders AppSearchConfigured when config.host is set & available', () => { - setMockValues({ errorConnectingMessage: '', config: { host: 'some.url' } }); - const wrapper = shallow(); - - expect(wrapper.find(AppSearchConfigured)).toHaveLength(1); - }); -}); - -describe('AppSearchUnconfigured', () => { - it('redirects to the Setup Guide', () => { - const wrapper = shallow(); - - expect(wrapper.find(Redirect)).toHaveLength(1); - }); -}); - -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(); - - beforeAll(() => { - setMockValues({ myRole: {}, renderHeaderActions }); - wrapper = shallow(); - }); - - it('renders header actions', () => { - expect(renderHeaderActions).toHaveBeenCalled(); - }); - - it('mounts AppLogic with passed initial data props', () => { - expect(AppLogic).toHaveBeenCalledWith(DEFAULT_INITIAL_APP_DATA); - }); - - it('renders engine routes', () => { - expect(wrapper.find(EnginesOverview)).toHaveLength(1); - expect(wrapper.find(EngineRouter)).toHaveLength(1); - }); - - describe('routes with ability checks', () => { - const runRouteAbilityCheck = (routeAbility: string, View: React.FC) => { - describe(View.name, () => { - it(`renders ${View.name} when user ${routeAbility} is true`, () => { - setMockValues({ myRole: { [routeAbility]: true } }); - rerender(wrapper); - expect(wrapper.find(View)).toHaveLength(1); - }); - - it(`does not render ${View.name} when user ${routeAbility} is false`, () => { - setMockValues({ myRole: { [routeAbility]: false } }); - rerender(wrapper); - expect(wrapper.find(View)).toHaveLength(0); - }); - }); - }; - - runRouteAbilityCheck('canViewSettings', Settings); - runRouteAbilityCheck('canViewAccountCredentials', Credentials); - runRouteAbilityCheck('canViewRoleMappings', RoleMappings); - runRouteAbilityCheck('canManageEngines', EngineCreation); - runRouteAbilityCheck('canManageMetaEngines', MetaEngineCreation); - }); - - describe('library', () => { - it('renders a library page in development', () => { - const OLD_ENV = process.env.NODE_ENV; - process.env.NODE_ENV = 'development'; - - rerender(wrapper); - - expect(wrapper.find(Library)).toHaveLength(1); - process.env.NODE_ENV = OLD_ENV; - }); - - it("doesn't in production", () => { - const OLD_ENV = process.env.NODE_ENV; - process.env.NODE_ENV = 'production'; - - rerender(wrapper); - - expect(wrapper.find(Library)).toHaveLength(0); - process.env.NODE_ENV = OLD_ENV; - }); - }); -}); 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 deleted file mode 100644 index 1a75cd58b1a24..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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, { useEffect } from 'react'; -import { Redirect } from 'react-router-dom'; - -import { useValues } from 'kea'; - -import { Routes, Route } from '@kbn/shared-ux-router'; - -import { InitialAppData } from '../../../common/types'; -import { KibanaLogic } from '../shared/kibana'; -import { EndpointsHeaderAction } from '../shared/layout/endpoints_header_action'; - -import { AppLogic } from './app_logic'; -import { Credentials } from './components/credentials'; -import { EngineRouter } from './components/engine'; -import { EngineCreation } from './components/engine_creation'; -import { EnginesOverview } from './components/engines'; -import { KibanaHeaderActions } from './components/layout'; -import { Library } from './components/library'; -import { MetaEngineCreation } from './components/meta_engine_creation'; -import { NotFound } from './components/not_found'; -import { RoleMappings } from './components/role_mappings'; -import { Settings } from './components/settings'; -import { SetupGuide } from './components/setup_guide'; -import { - ENGINE_CREATION_PATH, - ROOT_PATH, - SETUP_GUIDE_PATH, - SETTINGS_PATH, - CREDENTIALS_PATH, - USERS_AND_ROLES_PATH, - ENGINES_PATH, - ENGINE_PATH, - LIBRARY_PATH, - META_ENGINE_CREATION_PATH, -} from './routes'; - -export const AppSearch: React.FC = (props) => { - const { config } = useValues(KibanaLogic); - const showView = () => { - if (!config.host) { - return ; - } - return )} />; - }; - - return ( - - - - - {showView()} - - ); -}; - -export const AppSearchUnconfigured: React.FC = () => { - const { renderHeaderActions } = useValues(KibanaLogic); - renderHeaderActions(EndpointsHeaderAction); - - return ( - - - - - - ); -}; - -export const AppSearchConfigured: React.FC> = (props) => { - const { - showGateForm, - myRole: { - canManageEngines, - canManageMetaEngines, - canViewSettings, - canViewAccountCredentials, - canViewRoleMappings, - }, - } = useValues(AppLogic(props)); - const { renderHeaderActions } = useValues(KibanaLogic); - - useEffect(() => { - renderHeaderActions(KibanaHeaderActions); - }, []); - - return !showGateForm ? ( - - {process.env.NODE_ENV === 'development' && ( - - - - )} - - - - - - - {canManageEngines && ( - - - - )} - {canManageMetaEngines && ( - - - - )} - - - - {canViewSettings && ( - - - - )} - {canViewAccountCredentials && ( - - - - )} - {canViewRoleMappings && ( - - - - )} - - - - - ) : ( - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/app_search/jest.config.js deleted file mode 100644 index 7d591c369c18b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/jest.config.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../../../../..', - roots: ['/x-pack/plugins/enterprise_search/public/applications/app_search'], - collectCoverage: true, - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}', - '!/x-pack/plugins/enterprise_search/public/*.ts', - '!/x-pack/plugins/enterprise_search/server/*.ts', - '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', - ], - coverageDirectory: - '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/app_search', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts deleted file mode 100644 index 128af5adacfad..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 const ROOT_PATH = '/'; -export const SETUP_GUIDE_PATH = '/setup_guide'; -export const LIBRARY_PATH = '/library'; -export const SETTINGS_PATH = '/settings'; -export const CREDENTIALS_PATH = '/credentials'; - -export const USERS_AND_ROLES_PATH = '/users_and_roles'; - -export const ENGINES_PATH = '/engines'; -export const ENGINE_CREATION_PATH = `${ENGINES_PATH}/new`; // This is safe from conflicting with an :engineName path because new is a reserved name -export const ENGINE_PATH = `${ENGINES_PATH}/:engineName`; - -export const ENGINE_ANALYTICS_PATH = `${ENGINE_PATH}/analytics`; -export const ENGINE_ANALYTICS_TOP_QUERIES_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries`; -export const ENGINE_ANALYTICS_TOP_QUERIES_NO_CLICKS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_no_clicks`; -export const ENGINE_ANALYTICS_TOP_QUERIES_NO_RESULTS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_no_results`; -export const ENGINE_ANALYTICS_TOP_QUERIES_WITH_CLICKS_PATH = `${ENGINE_ANALYTICS_PATH}/top_queries_with_clicks`; -export const ENGINE_ANALYTICS_RECENT_QUERIES_PATH = `${ENGINE_ANALYTICS_PATH}/recent_queries`; -export const ENGINE_ANALYTICS_QUERY_DETAILS_PATH = `${ENGINE_ANALYTICS_PATH}/query_detail`; -export const ENGINE_ANALYTICS_QUERY_DETAIL_PATH = `${ENGINE_ANALYTICS_QUERY_DETAILS_PATH}/:query`; - -export const ENGINE_DOCUMENTS_PATH = `${ENGINE_PATH}/documents`; -export const ENGINE_DOCUMENT_DETAIL_PATH = `${ENGINE_DOCUMENTS_PATH}/:documentId`; - -export const ENGINE_SCHEMA_PATH = `${ENGINE_PATH}/schema`; -export const ENGINE_REINDEX_JOB_PATH = `${ENGINE_SCHEMA_PATH}/reindex_job/:reindexJobId`; - -export const ENGINE_CRAWLER_PATH = `${ENGINE_PATH}/crawler`; -export const ENGINE_CRAWLER_DOMAIN_PATH = `${ENGINE_CRAWLER_PATH}/domains/:domainId`; - -export const META_ENGINE_CREATION_PATH = `${ENGINES_PATH}/new_meta_engine`; // This is safe from conflicting with an :engineName path because engine names cannot have underscores -export const META_ENGINE_SOURCE_ENGINES_PATH = `${ENGINE_PATH}/engines`; - -export const ENGINE_RELEVANCE_TUNING_PATH = `${ENGINE_PATH}/relevance_tuning`; -export const ENGINE_SYNONYMS_PATH = `${ENGINE_PATH}/synonyms`; -export const ENGINE_RESULT_SETTINGS_PATH = `${ENGINE_PATH}/result_settings`; - -export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`; -export const ENGINE_CURATIONS_NEW_PATH = `${ENGINE_CURATIONS_PATH}/new`; -export const ENGINE_CURATION_PATH = `${ENGINE_CURATIONS_PATH}/:curationId`; -export const ENGINE_CURATION_SUGGESTION_PATH = `${ENGINE_CURATIONS_PATH}/suggestions/:query`; - -export const ENGINE_SEARCH_UI_PATH = `${ENGINE_PATH}/search_ui`; -export const ENGINE_API_LOGS_PATH = `${ENGINE_PATH}/api_logs`; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts deleted file mode 100644 index 0efa8880cca22..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 * from '../../../common/types/app_search'; -export type { Role, RoleTypes, AbilityTypes, ASRoleMapping, AdvanceRoleType } from './utils/role'; -export type { Engine } from './components/engine/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.test.ts deleted file mode 100644 index 74a2b78410db0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { mockUseParams } from '../../../__mocks__/react_router'; - -import { encodePathParams, generateEncodedPath, useDecodedParams } from '.'; - -describe('encodePathParams', () => { - it('encodeURIComponent()s all object values', () => { - const params = { - someValue: 'hello world???', - anotherValue: 'test!@#$%^&*[]/|;:"<>~`', - }; - expect(encodePathParams(params)).toEqual({ - someValue: 'hello%20world%3F%3F%3F', - anotherValue: 'test!%40%23%24%25%5E%26*%5B%5D%2F%7C%3B%3A%22%3C%3E~%60', - }); - }); -}); - -describe('generateEncodedPath', () => { - it('generates a react router path with encoded path parameters', () => { - expect( - generateEncodedPath('/values/:someValue/:anotherValue/new', { - someValue: 'hello world???', - anotherValue: 'test!@#$%^&*[]/|;:"<>~`', - }) - ).toEqual( - '/values/hello%20world%3F%3F%3F/test!%40%23%24%25%5E%26*%5B%5D%2F%7C%3B%3A%22%3C%3E~%60/new' - ); - }); -}); - -describe('useDecodedParams', () => { - it('decodeURIComponent()s all object values from useParams()', () => { - mockUseParams.mockReturnValue({ - someValue: 'hello%20world%3F%3F%3F', - anotherValue: 'test!%40%23%24%25%5E%26*%5B%5D%2F%7C%3B%3A%22%3C%3E~%60', - }); - expect(useDecodedParams()).toEqual({ - someValue: 'hello world???', - anotherValue: 'test!@#$%^&*[]/|;:"<>~`', - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.ts deleted file mode 100644 index 405d530ff20f3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { - encodePathParams, - generateEncodedPath, - useDecodedParams, -} from '../../../shared/encode_path_params'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/format_api_name/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/format_api_name/index.test.ts deleted file mode 100644 index bf244dee91551..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/format_api_name/index.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 { formatApiName } from '.'; - -describe('formatApiName', () => { - it('replaces non-alphanumeric characters with dashes', () => { - expect(formatApiName('f1 &&o$ 1 2 *&%da')).toEqual('f1-o-1-2-da'); - }); - - it('strips leading and trailing non-alphanumeric characters', () => { - expect(formatApiName('$$hello world**')).toEqual('hello-world'); - }); - - it('strips leading and trailing whitespace', () => { - expect(formatApiName(' test ')).toEqual('test'); - }); - - it('lowercases text', () => { - expect(formatApiName('SomeName')).toEqual('somename'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/format_api_name/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/format_api_name/index.ts deleted file mode 100644 index c7ec5e1d0b22d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/format_api_name/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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 const formatApiName = (rawName: string) => - rawName - .trim() - .replace(/[^a-zA-Z0-9]+/g, '-') // Replace all special/non-alphanumerical characters with dashes - .replace(/^[-]+|[-]+$/g, '') // Strip all leading and trailing dashes - .toLowerCase(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/formatted_date_time/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/formatted_date_time/index.tsx deleted file mode 100644 index e794ad6e704fd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/formatted_date_time/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { FormattedDateTime } from '../../../shared/formatted_date_time'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts deleted file mode 100644 index ccf1bf0bd9272..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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 { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; - -import { recursivelyFetchEngines } from '.'; - -describe('recursivelyFetchEngines', () => { - const { http } = mockHttpValues; - - const MOCK_PAGE_1 = { - meta: { - page: { current: 1, total_pages: 3 }, - }, - results: [{ name: 'source-engine-1' }], - }; - const MOCK_PAGE_2 = { - meta: { - page: { current: 2, total_pages: 3 }, - }, - results: [{ name: 'source-engine-2' }], - }; - const MOCK_PAGE_3 = { - meta: { - page: { current: 3, total_pages: 3 }, - }, - results: [{ name: 'source-engine-3' }], - }; - const MOCK_CALLBACK = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('recursively calls the passed API endpoint and returns all engines to the onComplete callback', async () => { - http.get - .mockReturnValueOnce(Promise.resolve(MOCK_PAGE_1)) - .mockReturnValueOnce(Promise.resolve(MOCK_PAGE_2)) - .mockReturnValueOnce(Promise.resolve(MOCK_PAGE_3)); - - recursivelyFetchEngines({ - endpoint: '/internal/app_search/engines/some-engine/source_engines', - onComplete: MOCK_CALLBACK, - }); - await nextTick(); - - expect(http.get).toHaveBeenCalledTimes(3); // Called once for each page - expect(http.get).toHaveBeenCalledWith( - '/internal/app_search/engines/some-engine/source_engines', - { - query: { - 'page[current]': 1, - 'page[size]': 25, - }, - } - ); - - expect(MOCK_CALLBACK).toHaveBeenCalledWith([ - { name: 'source-engine-1' }, - { name: 'source-engine-2' }, - { name: 'source-engine-3' }, - ]); - }); - - it('passes optional query params', () => { - recursivelyFetchEngines({ - endpoint: '/internal/app_search/engines/some-engine/engines', - onComplete: MOCK_CALLBACK, - query: { type: 'indexed' }, - }); - - expect(http.get).toHaveBeenCalledWith('/internal/app_search/engines/some-engine/engines', { - query: { - 'page[current]': 1, - 'page[size]': 25, - type: 'indexed', - }, - }); - }); - - it('passes optional custom page sizes', () => { - recursivelyFetchEngines({ - endpoint: '/over_9000', - onComplete: MOCK_CALLBACK, - pageSize: 9001, - }); - - expect(http.get).toHaveBeenCalledWith('/over_9000', { - query: { - 'page[current]': 1, - 'page[size]': 9001, - }, - }); - }); - - itShowsServerErrorAsFlashMessage(http.get, () => { - recursivelyFetchEngines({ endpoint: '/error', onComplete: MOCK_CALLBACK }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.ts deleted file mode 100644 index 797e89bd68b69..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 { flashAPIErrors } from '../../../shared/flash_messages'; -import { HttpLogic } from '../../../shared/http'; - -import { EngineDetails } from '../../components/engine/types'; -import { EnginesAPIResponse } from '../../components/engines/types'; - -interface Params { - endpoint: string; - onComplete: (engines: EngineDetails[]) => void; - query?: object; - pageSize?: number; -} - -export const recursivelyFetchEngines = ({ - endpoint, - onComplete, - query = {}, - pageSize = 25, -}: Params) => { - const { http } = HttpLogic.values; - - let enginesAccumulator: EngineDetails[] = []; - - const fetchEngines = async (page = 1) => { - try { - const { meta, results }: EnginesAPIResponse = await http.get(endpoint, { - query: { - 'page[current]': page, - 'page[size]': pageSize, - ...query, - }, - }); - - enginesAccumulator = [...enginesAccumulator, ...results]; - - if (page >= meta.page.total_pages) { - onComplete(enginesAccumulator); - } else { - fetchEngines(page + 1); - } - } catch (e) { - flashAPIErrors(e); - } - }; - - fetchEngines(); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts deleted file mode 100644 index b020a4150d5fc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 { formatResult } from '.'; - -describe('formatResult', () => { - it('format the result', () => { - const data = { - id: { raw: 'doc-id' }, - text_field: { raw: 'text value' }, - numeric_field: { raw: 21 }, - multivalued_field: { raw: ['value_1', 'value_2'] }, - 'simple_object.flattened': { raw: ['value_1', 'value_2'] }, - raw: { raw: 'raw_value' }, - snippet: { snippet: 'snipet_value' }, - }; - - expect(formatResult(data)).toEqual(data); - }); - - describe('with nested objects', () => { - describe('single value field', () => { - it('transform nested field values', () => { - expect( - formatResult({ - id: { raw: 'doc-id' }, - nested_object: { - subfield_1: { raw: ['value 1', 'value 2'] }, - subfield_2: { raw: 'value 3' }, - }, - }) - ).toEqual({ - id: { raw: 'doc-id' }, - nested_object: { - raw: { - subfield_1: ['value 1', 'value 2'], - subfield_2: 'value 3', - }, - }, - }); - }); - }); - - describe('multi-valued field', () => { - it('transform nested field values', () => { - expect( - formatResult({ - id: { raw: 'doc-id' }, - nested_object: [ - { - subfield_1: { raw: ['value 1', 'value 2'] }, - subfield_2: { raw: 'value 3' }, - raw: { raw: 'raw_value' }, - snippet: { raw: 'snippert_value' }, - }, - { - subfield_1: { raw: 'value 4' }, - raw: { raw: ['raw_value'] }, - snippet: { raw: ['snippert_value'] }, - }, - ], - }) - ).toEqual({ - id: { raw: 'doc-id' }, - nested_object: { - raw: [ - { - subfield_1: ['value 1', 'value 2'], - subfield_2: 'value 3', - raw: 'raw_value', - snippet: 'snippert_value', - }, - { - subfield_1: 'value 4', - raw: ['raw_value'], - snippet: ['snippert_value'], - }, - ], - }, - }); - }); - }); - }); - - it('does not consider _meta field as a nested field', () => { - const data = { - id: { raw: 'doc-id' }, - _meta: { id: '1', _score: 12, engine: 'foo-engine' }, - }; - expect(formatResult(data)).toEqual(data); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.ts deleted file mode 100644 index 673f577c0033e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 { - FieldValue, - NestedFieldValue, - ResultMeta, - SimpleFieldValue, - Snippet, -} from '../../components/result/types'; - -interface SearchApiWrappedFieldValue { - raw?: SimpleFieldValue; - snippet?: Snippet; -} -type SearchApiNestedFieldValue = - | { [key: string]: SearchApiNestedFieldValue | SearchApiWrappedFieldValue } - | SearchApiNestedFieldValue[]; -type SearchApiFieldValue = ResultMeta | SearchApiWrappedFieldValue | SearchApiNestedFieldValue; - -function isResultMeta(fieldName: string, _: SearchApiFieldValue): _ is ResultMeta { - return fieldName === '_meta'; -} - -function isFieldValueWrapper( - fieldValue: SearchApiFieldValue -): fieldValue is SearchApiWrappedFieldValue { - return ( - fieldValue && - Object.entries(fieldValue).reduce((isValueWrapper: boolean, [k, v]) => { - if (k !== 'raw' && k !== 'snippet') { - return false; - } - - if (v === null) { - return isValueWrapper; - } - - return (Array.isArray(v) ? v : [v]).reduce((isScalar, currentValue) => { - return isScalar && currentValue !== null && typeof currentValue !== 'object'; - }, isValueWrapper); - }, true) - ); -} - -function isNestedFieldValue( - fieldValue: SearchApiFieldValue -): fieldValue is SearchApiNestedFieldValue { - if (Array.isArray(fieldValue)) { - return fieldValue.reduce( - (isNested: boolean, current) => isNested || isNestedFieldValue(current), - false - ); - } - - return fieldValue != null && typeof fieldValue === 'object' && !isFieldValueWrapper(fieldValue); -} - -function formatNestedFieldValue( - fieldValue: SearchApiNestedFieldValue | SearchApiWrappedFieldValue -): NestedFieldValue { - if (Array.isArray(fieldValue)) { - return fieldValue.map(formatNestedFieldValue); - } - - if (fieldValue !== null && typeof fieldValue === 'object') { - return Object.entries(fieldValue).reduce( - (formattedFieldValue, [nestedFieldName, currentValue]) => { - return { - ...formattedFieldValue, - [nestedFieldName]: isFieldValueWrapper(currentValue) - ? currentValue.raw - : formatNestedFieldValue(currentValue), - }; - }, - {} - ); - } - - return fieldValue; -} - -export function formatResult( - result: Record -): Record { - return Object.entries(result).reduce((acc, [fieldName, fieldValue]) => { - if (!isResultMeta(fieldName, fieldValue) && isNestedFieldValue(fieldValue)) { - return { ...acc, [fieldName]: { raw: formatNestedFieldValue(fieldValue) } }; - } - - return { ...acc, [fieldName]: fieldValue }; - }, {}); -} - -export function formatResultWithoutMeta( - result: Record -): Record { - return Object.entries(result).reduce((acc, [fieldName, fieldValue]) => { - if (isResultMeta(fieldName, fieldValue)) { - return { ...acc }; - } - - if (isNestedFieldValue(fieldValue)) { - return { ...acc, [fieldName]: { raw: formatNestedFieldValue(fieldValue) } }; - } - - return { ...acc, [fieldName]: fieldValue }; - }, {}); -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts deleted file mode 100644 index af0a31945d61f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 { DEFAULT_INITIAL_APP_DATA } from '../../../../../common/__mocks__'; - -import { getRoleAbilities } from '.'; - -describe('getRoleAbilities', () => { - const mockRole = DEFAULT_INITIAL_APP_DATA.appSearch.role as any; - - it('transforms server role data into a flat role obj with helper shorthands', () => { - expect(getRoleAbilities(mockRole)).toEqual({ - id: 'account_id:somestring|user_oid:somestring', - roleType: 'owner', - availableRoleTypes: ['owner', 'admin'], - credentialTypes: ['admin', 'private', 'search'], - canAccessAllEngines: true, - can: expect.any(Function), - // Has access - canViewAccountCredentials: true, - canManageEngines: true, - canManageMetaEngines: true, - // Does not have access - canViewMetaEngines: false, - canViewEngineAnalytics: false, - canViewEngineApiLogs: false, - canViewEngineCrawler: false, - canViewEngineCredentials: false, - canViewEngineDocuments: false, - canViewEngineSchema: false, - canViewEngineQueryTester: false, - canViewMetaEngineSourceEngines: false, - canViewSettings: false, - canViewRoleMappings: false, - canManageLogSettings: false, - canManageSettings: false, - canManageEngineCrawler: false, - canManageEngineDocuments: false, - canManageEngineSynonyms: false, - canManageEngineCredentials: false, - canManageEngineCurations: false, - canManageEngineRelevanceTuning: false, - canManageEngineSearchUi: false, - canManageEngineResultSettings: false, - canManageEngineSchema: false, - canManageMetaEngineSourceEngines: false, - }); - }); - - describe('can()', () => { - it('sets view abilities to true if manage abilities are true', () => { - const role = { - ...mockRole, - ability: { view: [], manage: ['account_settings'] }, - }; - - const myRole = getRoleAbilities(role); - - expect(myRole.canViewSettings).toEqual(true); - expect(myRole.canManageSettings).toEqual(true); - }); - - it('returns false for invalid actions & subjects', () => { - const myRole = getRoleAbilities(mockRole); - - expect(myRole.can('hello' as any, 'world')).toEqual(false); - expect(myRole.can('edit', 'fakeSubject')).toEqual(false); - }); - }); - - describe('canManageMetaEngines', () => { - const canManageEngines = { ability: { manage: ['account_engines'] } }; - - it('returns true when the user can manage any engines and the account has a platinum license', () => { - const myRole = getRoleAbilities({ ...mockRole, ...canManageEngines }); - - expect(myRole.canManageMetaEngines).toEqual(true); - }); - - it('returns true when the user can manage any engines but the account does not have a platinum license', () => { - const myRole = getRoleAbilities({ ...mockRole, ...canManageEngines }); - - expect(myRole.canManageMetaEngines).toEqual(true); - }); - - it('returns false when has a platinum license but the user cannot manage any engines', () => { - const myRole = getRoleAbilities({ ...mockRole, ability: { manage: [] } }); - - expect(myRole.canManageMetaEngines).toEqual(false); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts deleted file mode 100644 index 2196b40f2aa26..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 { Account } from '../../types'; - -import { RoleTypes, AbilityTypes, Role } from './types'; - -/** - * Transforms the `role` data we receive from the Enterprise Search - * server into a more convenient format for front-end use - */ -export const getRoleAbilities = (role: Account['role']): Role => { - // Role ability function helpers - const myRole = { - can: (action: AbilityTypes, subject: string): boolean => { - return ( - role?.ability?.manage?.includes(subject) || - (Array.isArray(role.ability[action]) && role.ability[action].includes(subject)) - ); - }, - }; - - // Clone top-level role props, and move some props out of `ability` and into the top-level for convenience - const topLevelProps = { - id: role.id, - roleType: role.roleType as RoleTypes, - availableRoleTypes: role.ability.availableRoleTypes as RoleTypes[], - credentialTypes: role.ability.credentialTypes, - }; - - // Ability shorthands (also in top level of role obj for convenience) - // Example usage: `const { myRole: { canViewSettings } } = useValues(AppLogic);` - const abilities = { - canAccessAllEngines: role.ability.accessAllEngines, - canViewMetaEngines: myRole.can('view', 'account_meta_engines'), - canViewAccountCredentials: myRole.can('view', 'account_credentials'), - canViewEngineAnalytics: myRole.can('view', 'engine_analytics'), - canViewEngineApiLogs: myRole.can('view', 'engine_api_logs'), - canViewEngineCrawler: myRole.can('view', 'engine_crawler'), - canViewEngineCredentials: myRole.can('view', 'engine_credentials'), - canViewEngineDocuments: myRole.can('view', 'engine_documents'), - canViewEngineSchema: myRole.can('view', 'engine_schema'), - canViewEngineQueryTester: myRole.can('view', 'engine_query_tester'), - canViewMetaEngineSourceEngines: myRole.can('view', 'meta_engine_source_engines'), - canViewSettings: myRole.can('view', 'account_settings'), - canViewRoleMappings: myRole.can('view', 'role_mappings'), - canManageEngines: myRole.can('manage', 'account_engines'), - canManageMetaEngines: myRole.can('manage', 'account_engines'), - canManageLogSettings: myRole.can('manage', 'account_log_settings'), - canManageSettings: myRole.can('manage', 'account_settings'), - canManageEngineCrawler: myRole.can('manage', 'engine_crawler'), - canManageEngineDocuments: myRole.can('manage', 'engine_documents'), - canManageEngineSynonyms: myRole.can('manage', 'engine_synonyms'), - canManageEngineCredentials: myRole.can('manage', 'engine_credentials'), - canManageEngineCurations: myRole.can('manage', 'engine_curations'), - canManageEngineRelevanceTuning: myRole.can('manage', 'engine_relevance_tuning'), - canManageEngineResultSettings: myRole.can('manage', 'engine_result_settings'), - canManageEngineSchema: myRole.can('manage', 'engine_schema'), - canManageEngineSearchUi: myRole.can('manage', 'engine_reference_ui'), - canManageMetaEngineSourceEngines: myRole.can('manage', 'meta_engine_source_engines'), - }; - - return Object.assign(myRole, topLevelProps, abilities); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts deleted file mode 100644 index f63b80e4ce894..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 { roleHasScopedEngines } from '.'; - -describe('roleHasScopedEngines', () => { - it('returns false for owner and admin roles', () => { - expect(roleHasScopedEngines('owner')).toEqual(false); - expect(roleHasScopedEngines('admin')).toEqual(false); - }); - - it('returns true for dev, editor, and analyst roles', () => { - expect(roleHasScopedEngines('dev')).toEqual(true); - expect(roleHasScopedEngines('editor')).toEqual(true); - expect(roleHasScopedEngines('analyst')).toEqual(true); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.ts deleted file mode 100644 index 7a16f6c76a417..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/has_scoped_engines.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 { RoleTypes } from './types'; - -/** - * Small utility helper for determining if a given role can have scoped engines - */ -export const roleHasScopedEngines = (roleType: RoleTypes): boolean => { - const unscopedRoles = ['dev', 'editor', 'analyst']; - return unscopedRoles.includes(roleType); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts deleted file mode 100644 index d0a465d9eb4e2..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 * from './types'; -export { getRoleAbilities } from './get_role_abilities'; -export { roleHasScopedEngines } from './has_scoped_engines'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts deleted file mode 100644 index f125a9dd13aa5..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { RoleMapping } from '../../../shared/types'; -import { Engine } from '../../components/engine/types'; - -export type RoleTypes = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst'; -export type AbilityTypes = 'manage' | 'edit' | 'view'; - -export interface Role { - id: string; - roleType: RoleTypes; - availableRoleTypes: RoleTypes[]; - credentialTypes: string[]; - canAccessAllEngines: boolean; - can(action: AbilityTypes, subject: string): boolean; - canViewMetaEngines: boolean; - canViewAccountCredentials: boolean; - canViewEngineAnalytics: boolean; - canViewEngineApiLogs: boolean; - canViewEngineCrawler: boolean; - canViewEngineCredentials: boolean; - canViewEngineDocuments: boolean; - canViewEngineSchema: boolean; - canViewEngineQueryTester: boolean; - canViewMetaEngineSourceEngines: boolean; - canViewSettings: boolean; - canViewRoleMappings: boolean; - canManageEngines: boolean; - canManageMetaEngines: boolean; - canManageLogSettings: boolean; - canManageSettings: boolean; - canManageEngineCrawler: boolean; - canManageEngineDocuments: boolean; - canManageEngineSynonyms: boolean; - canManageEngineCredentials: boolean; - canManageEngineCurations: boolean; - canManageEngineRelevanceTuning: boolean; - canManageEngineResultSettings: boolean; - canManageEngineSchema: boolean; - canManageEngineSearchUi: boolean; - canManageMetaEngineSourceEngines: boolean; -} - -export interface ASRoleMapping extends RoleMapping { - accessAllEngines: boolean; - engines: Engine[]; -} - -export interface AdvanceRoleType { - id: RoleTypes; - description: string; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/applications/jest.config.js index 1e04c0845ec9f..238a735c9661e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/applications/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/applications', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/jest.config.js index ab90da605f2b3..6da635b889c0f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/elasticsearch', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/index.ts deleted file mode 100644 index f15a3acbb6394..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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 { searchEngines } from './search_engines.mock'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_engines.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_engines.mock.ts deleted file mode 100644 index 775f7dde83d87..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_engines.mock.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 { Engine } from '../../app_search/components/engine/types'; - -// TODO populate them -export const searchEngines = [ - { name: 'My First Search Engine' }, - { name: 'Another Search Engine' }, - { name: 'Dharma Initiative Research' }, - { name: 'Flight 815 Customer Feedback' }, - { name: 'The Swan Station Manuals' }, - { name: 'The Hydra Station Manuals' }, -] as Engine[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/stats/fetch_cloud_health_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/stats/fetch_cloud_health_api_logic.ts deleted file mode 100644 index 06a48370b1d15..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/stats/fetch_cloud_health_api_logic.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { CloudHealth } from '../../../../../common/stats'; - -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; -import { HttpLogic } from '../../../shared/http'; - -export type FetchCloudHealthResponse = CloudHealth; - -export const fetchCloudHealth = async () => { - const route = '/internal/enterprise_search/stats/cloud_health'; - return await HttpLogic.values.http.get(route); -}; - -export const FetchCloudHealthApiLogic = createApiLogic( - ['enterprise_search_content', 'fetch_cloud_health_api_logic'], - fetchCloudHealth -); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx index 47f2226fe976e..19c016fa5c756 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors.tsx @@ -25,7 +25,6 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { handlePageChange } from '../../../shared/table_pagination'; import { @@ -64,11 +63,11 @@ export const Connectors: React.FC = ({ isCrawler }) => { const { fetchConnectors, onPaginate, setIsFirstRequest, openDeleteModal } = useActions(ConnectorsLogic); const { data, isLoading, searchParams, isEmpty, connectors } = useValues(ConnectorsLogic); - const { errorConnectingMessage } = useValues(HttpLogic); const [searchQuery, setSearchValue] = useState(''); const [showMoreOptionsPopover, setShowMoreOptionsPopover] = useState(false); const [showDefaultSettingsFlyout, setShowDefaultSettingsFlyout] = useState(false); const { productFeatures } = useValues(KibanaLogic); + const hasElasticCrawler = false; // temp variable replacing checking error connecting to entSearch node useEffect(() => { setIsFirstRequest(); @@ -203,7 +202,7 @@ export const Connectors: React.FC = ({ isCrawler }) => { { - const { errorConnectingMessage } = useValues(HttpLogic); return ( {

    } actions={ - Boolean(errorConnectingMessage) ? ( - - {i18n.translate( - 'xpack.enterpriseSearch.crawlerEmptyState.openSourceCrawlerButtonLabel', - { - defaultMessage: 'Source code', - } - )} - - ) : ( - KibanaLogic.values.navigateToUrl(NEW_CRAWLER_PATH)} - > - {i18n.translate('xpack.enterpriseSearch.crawlerEmptyState.newWebCrawlerButtonLabel', { - defaultMessage: 'New web crawler', - })} - - ) + + {i18n.translate( + 'xpack.enterpriseSearch.crawlerEmptyState.openSourceCrawlerButtonLabel', + { + defaultMessage: 'Source code', + } + )} + } />
    diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.ts index 85e7db0f2a374..3794a81b2a664 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.ts @@ -9,9 +9,8 @@ import { kea, MakeLogicType } from 'kea'; import { ErrorCode } from '../../../../../../common/types/error_codes'; -import { generateEncodedPath } from '../../../../app_search/utils/encode_path_params'; - import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { generateEncodedPath } from '../../../../shared/encode_path_params'; import { KibanaLogic } from '../../../../shared/kibana'; import { AddConnectorApiLogic, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx index b7d38da28bcec..489a65e97775f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useActions, useValues } from 'kea'; @@ -21,8 +21,6 @@ import { } from '../../../../shared/licensing_callout/licensing_callout'; import { AddConnectorApiLogic } from '../../../api/connector/add_connector_api_logic'; -import { FetchCloudHealthApiLogic } from '../../../api/stats/fetch_cloud_health_api_logic'; - import { errorToText } from '../utils/error_to_text'; import { AddConnectorLogic } from './add_connector_logic'; @@ -61,14 +59,6 @@ export const MethodConnector: React.FC = ({ const isGated = isNative && !isCloud && !hasPlatinumLicense; - const { makeRequest: fetchCloudHealth } = useActions(FetchCloudHealthApiLogic); - - useEffect(() => { - if (isCloud) { - fetchCloudHealth({}); - } - }, [isCloud]); - return ( {isGated && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_crawler/method_crawler.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_crawler/method_crawler.tsx index e4842f765b4ad..29197140b00ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_crawler/method_crawler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_crawler/method_crawler.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Status } from '../../../../../../common/types/api'; import { docLinks } from '../../../../shared/doc_links'; -import { HttpLogic } from '../../../../shared/http'; import { KibanaLogic } from '../../../../shared/kibana'; import { LicensingLogic } from '../../../../shared/licensing'; import { @@ -30,8 +29,8 @@ export const MethodCrawler: React.FC = () => { const { makeRequest } = useActions(CreateCrawlerIndexApiLogic); const { isCloud } = useValues(KibanaLogic); const { hasPlatinumLicense } = useValues(LicensingLogic); - const { errorConnectingMessage } = useValues(HttpLogic); + const hasElasticCrawler = false; // temp variable replacing checking error connecting to entSearch node const isGated = !isCloud && !hasPlatinumLicense; MethodCrawlerLogic.mount(); @@ -46,8 +45,8 @@ export const MethodCrawler: React.FC = () => { makeRequest({ indexName, language })} - disabled={isGated || Boolean(errorConnectingMessage)} buttonLoading={status === Status.LOADING} docsUrl={docLinks.crawlerOverview} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index.tsx index 0244e9acbe353..ca720f904ce9c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index.tsx @@ -16,7 +16,6 @@ import { INGESTION_METHOD_IDS } from '../../../../../common/constants'; import { ProductFeatures } from '../../../../../common/types'; -import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; import { NEW_API_PATH, NEW_CRAWLER_PATH, NEW_INDEX_SELECT_CONNECTOR_PATH } from '../../routes'; @@ -28,15 +27,14 @@ import { NewIndexCard } from './new_index_card'; const getAvailableMethodOptions = (productFeatures: ProductFeatures): INGESTION_METHOD_IDS[] => { return [ INGESTION_METHOD_IDS.API, - ...(productFeatures.hasWebCrawler ? [INGESTION_METHOD_IDS.CRAWLER] : []), ...(productFeatures.hasConnectors ? [INGESTION_METHOD_IDS.CONNECTOR] : []), ]; }; export const NewIndex: React.FC = () => { - const { config, productFeatures } = useValues(KibanaLogic); + const { productFeatures } = useValues(KibanaLogic); const availableIngestionMethodOptions = getAvailableMethodOptions(productFeatures); - const { errorConnectingMessage } = useValues(HttpLogic); + const hasElasticCrawler = false; // temp variable replacing checking error connecting to entSearch node return ( { {availableIngestionMethodOptions.map((type) => ( { if (type === INGESTION_METHOD_IDS.CONNECTOR) { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts index 1bedf46cfd09e..db694956a00b1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts @@ -16,9 +16,6 @@ import { flashIndexCreatedToast } from './new_index_created_toast'; import { NewSearchIndexLogic, NewSearchIndexValues } from './new_search_index_logic'; jest.mock('./new_index_created_toast', () => ({ flashIndexCreatedToast: jest.fn() })); -jest.mock('../../../shared/kibana/kibana_logic', () => ({ - KibanaLogic: { values: { productAccess: { hasAppSearchAccess: true } } }, -})); const DEFAULT_VALUES: NewSearchIndexValues = { data: undefined as any, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.ts index 3c1e531b1c6d9..c5497d3a3e135 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.ts @@ -8,7 +8,6 @@ import { kea, MakeLogicType } from 'kea'; import { Actions } from '../../../shared/api_logic/create_api_logic'; -import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; import { AddConnectorApiLogic, AddConnectorApiLogicArgs, @@ -88,15 +87,12 @@ export const NewSearchIndexLogic = kea ({ apiIndexCreated: () => { - if (!KibanaLogic.values.productAccess.hasAppSearchAccess) return; flashIndexCreatedToast(); }, connectorIndexCreated: () => { - if (!KibanaLogic.values.productAccess.hasAppSearchAccess) return; flashIndexCreatedToast(); }, crawlerIndexCreated: () => { - if (!KibanaLogic.values.productAccess.hasAppSearchAccess) return; flashIndexCreatedToast(); }, setRawName: async (_, breakpoint) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts index 6b5ee3826a27e..66e53fdab733c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts @@ -26,7 +26,7 @@ import { IndexNameLogic } from './index_name_logic'; import { IndexViewLogic } from './index_view_logic'; jest.mock('../../../shared/kibana/kibana_logic', () => ({ - KibanaLogic: { values: { productAccess: { hasDocumentLevelSecurityEnabled: true } } }, + KibanaLogic: { values: { productFeatures: { hasDocumentLevelSecurityEnabled: true } } }, })); // We can't test fetchTimeOutId because this will get set whenever the logic is created diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx index e8876a7c56818..d20dbb0201ec3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx @@ -84,17 +84,6 @@ export const SearchIndex: React.FC = () => { const indicesItems = useIndicesNav(); - useEffect(() => { - const subscription = guidedOnboarding?.guidedOnboardingApi - ?.isGuideStepActive$('appSearch', 'add_data') - .subscribe((isStepActive) => { - if (isStepActive && index?.count) { - guidedOnboarding?.guidedOnboardingApi?.completeGuideStep('appSearch', 'add_data'); - } - }); - return () => subscription?.unsubscribe(); - }, [guidedOnboarding, index?.count]); - useEffect(() => { const subscription = guidedOnboarding?.guidedOnboardingApi ?.isGuideStepActive$('websiteSearch', 'add_data') diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx index b0c887a4c2228..a831e7cf4ad1b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/header_actions/syncs_context_menu.tsx @@ -25,7 +25,6 @@ import { i18n } from '@kbn/i18n'; import { ConnectorStatus, IngestionStatus } from '@kbn/search-connectors'; import { Status } from '../../../../../../common/types/api'; -import { HttpLogic } from '../../../../shared/http'; import { KibanaLogic } from '../../../../shared/kibana'; import { CancelSyncsApiLogic } from '../../../api/connector/cancel_syncs_api_logic'; import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; @@ -39,14 +38,13 @@ export interface SyncsContextMenuProps { } export const SyncsContextMenu: React.FC = ({ disabled = false }) => { - const { config, productFeatures } = useValues(KibanaLogic); + const { productFeatures } = useValues(KibanaLogic); const { ingestionStatus, isCanceling, isSyncing, isWaitingForSync } = useValues(IndexViewLogic); const { connector, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature } = useValues(ConnectorViewLogic); const { status } = useValues(CancelSyncsApiLogic); const { startSync, startIncrementalSync, startAccessControlSync, cancelSyncs } = useActions(SyncsLogic); - const { errorConnectingMessage } = useValues(HttpLogic); const [isPopoverOpen, setPopover] = useState(false); const togglePopover = () => setPopover(!isPopoverOpen); @@ -80,7 +78,7 @@ export const SyncsContextMenu: React.FC = ({ disabled = f const shouldShowIncrementalSync = productFeatures.hasIncrementalSyncEnabled && hasIncrementalSyncFeature; - const isEnterpriseSearchNotAvailable = Boolean(config.host && errorConnectingMessage); + const isEnterpriseSearchNotAvailable = true; const isSyncsDisabled = (connector?.is_native && isEnterpriseSearchNotAvailable) || ingestionStatus === IngestionStatus.INCOMPLETE || diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/jest.config.js index a55b8bbc715f4..fbc954aa70c30 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/jest.config.js @@ -21,8 +21,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/app_search_product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/app_search_product_card.tsx deleted file mode 100644 index f8dac98f048ad..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/app_search_product_card.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { ProductCard } from '../product_card'; - -export interface AppSearchProductCardProps { - hasBorder: boolean; - hasShadow: boolean; -} - -export const AppSearchProductCard: React.FC = ({ - hasBorder = true, - hasShadow = true, -}) => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/enterprise_search_product_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/enterprise_search_product_card.test.tsx deleted file mode 100644 index b1c0394e80235..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/enterprise_search_product_card.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { mount } from 'enzyme'; - -import { AppSearchProductCard } from './app_search_product_card'; -import { EnterpriseSearchProductCard } from './enterprise_search_product_card'; -import { WorkplaceSearchProductCard } from './workplace_search_product_card'; - -describe('EnterpriseSearchProductCard', () => { - beforeEach(() => { - setMockValues({ config: { host: 'localhost' } }); - }); - - it('renders both services with access', () => { - const wrapper = mount( - - ); - - expect(wrapper.find(AppSearchProductCard)).toHaveLength(1); - expect(wrapper.find(WorkplaceSearchProductCard)).toHaveLength(1); - }); - it('can render just app search', () => { - const wrapper = mount( - - ); - - expect(wrapper.find(AppSearchProductCard)).toHaveLength(1); - expect(wrapper.find(WorkplaceSearchProductCard)).toHaveLength(0); - }); - it('can render just workplace search', () => { - const wrapper = mount( - - ); - - expect(wrapper.find(AppSearchProductCard)).toHaveLength(0); - expect(wrapper.find(WorkplaceSearchProductCard)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/enterprise_search_product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/enterprise_search_product_card.tsx deleted file mode 100644 index d76940d1721b3..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/enterprise_search_product_card.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { - ENTERPRISE_SEARCH_PRODUCT_NAME, - ENTERPRISE_SEARCH_CONTENT_PLUGIN, -} from '../../../../../common/constants'; -import { docLinks } from '../../../shared/doc_links'; -import { ProductCard } from '../product_card'; - -import { AppSearchProductCard } from './app_search_product_card'; -import { WorkplaceSearchProductCard } from './workplace_search_product_card'; - -export interface EnterpriseSearchProductCardProps { - hasAppSearchAccess: boolean; - hasWorkplaceSearchAccess: boolean; - isWorkplaceSearchAdmin: boolean; -} - -export const EnterpriseSearchProductCard = ({ - hasAppSearchAccess, - hasWorkplaceSearchAccess, - isWorkplaceSearchAdmin, -}: EnterpriseSearchProductCardProps) => { - const rightPanelItems: React.ReactNode[] = []; - if (hasAppSearchAccess) { - rightPanelItems.push(); - } - if (hasWorkplaceSearchAccess) { - rightPanelItems.push( - - ); - } - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/ingestion_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/ingestion_selector.tsx index 7a36c6a973ec2..cf0db33081ea3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/ingestion_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/ingestion_selector.tsx @@ -18,37 +18,30 @@ import { i18n } from '@kbn/i18n'; import { ENTERPRISE_SEARCH_CONTENT_PLUGIN, ENTERPRISE_SEARCH_ELASTICSEARCH_URL, - CRAWLER, } from '../../../../../common/constants'; import apiLogo from '../../../../assets/images/api_image.png'; import fileUploadLogo from '../../../../assets/images/file_upload_logo.svg'; import sampleDataLogo from '../../../../assets/images/sample_data_logo.svg'; import connectorLogo from '../../../../assets/images/search_connector.svg'; -import crawlerLogo from '../../../../assets/images/search_crawler.svg'; import languageClientsLogo from '../../../../assets/images/search_language_clients.svg'; import { IngestionCard } from '../../../enterprise_search_content/components/shared/ingestion_card/ingestion_card'; import { NEW_API_PATH, - NEW_CRAWLER_PATH, NEW_INDEX_SELECT_CONNECTOR_PATH, } from '../../../enterprise_search_content/routes'; -import { HttpLogic } from '../../../shared/http/http_logic'; import { ConnectorIcon } from '../../../shared/icons/connector'; -import { CrawlerIcon } from '../../../shared/icons/crawler'; -import { GithubIcon } from '../../../shared/icons/github_icon'; + import { KibanaLogic } from '../../../shared/kibana'; export const IngestionSelector: React.FC = () => { const { application: { navigateToApp }, - config, productFeatures, } = useValues(KibanaLogic); - const { errorConnectingMessage } = useValues(HttpLogic); - const crawlerDisabled = Boolean(errorConnectingMessage || !config.host); + return ( <> @@ -75,45 +68,6 @@ export const IngestionSelector: React.FC = () => { )} /> - {productFeatures.hasWebCrawler && ( - - - - )} {productFeatures.hasConnectors && ( { - it('renders with url when admin', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('url')).toEqual(WORKPLACE_SEARCH_PLUGIN.URL); - }); - it('renders with non-admin url when not admin', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(ProductCard)).toHaveLength(1); - expect(wrapper.find(ProductCard).prop('url')).toEqual(WORKPLACE_SEARCH_PLUGIN.NON_ADMIN_URL); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/workplace_search_product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/workplace_search_product_card.tsx deleted file mode 100644 index 94d3c9bd96bfc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/workplace_search_product_card.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; -import { ProductCard } from '../product_card'; - -export interface WorkplaceSearchProductCardProps { - hasBorder: boolean; - hasShadow: boolean; - isWorkplaceSearchAdmin: boolean; -} - -export const WorkplaceSearchProductCard: React.FC = ({ - hasBorder = true, - hasShadow = true, - isWorkplaceSearchAdmin, -}) => ( - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/e2e/overview.cy.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/e2e/overview.cy.ts index 45bd8f68a85fb..26fbeee31bcff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/e2e/overview.cy.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/cypress/e2e/overview.cy.ts @@ -17,16 +17,6 @@ context('Enterprise Search Overview', () => { cy.visit(overviewPath); cy.contains('Welcome to Elastic Enterprise Search'); - cy.get('[data-test-subj="appSearchProductCard"]') - .contains('Open App Search') - .should('have.attr', 'href') - .and('match', /app_search/); - - cy.get('[data-test-subj="workplaceSearchProductCard"]') - .contains('Open Workplace Search') - .should('have.attr', 'href') - .and('match', /workplace_search/); - checkA11y(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/jest.config.js index fd5a6db3b8e0c..233671a5c8b62 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/jest.config.js @@ -21,8 +21,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx index 81827172acc6d..4c4a038ac5b01 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx @@ -28,10 +28,8 @@ import { securityMock } from '@kbn/security-plugin/public/mocks'; import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; -import { AppSearch } from './app_search'; import { EnterpriseSearchOverview } from './enterprise_search_overview'; import { KibanaLogic } from './shared/kibana'; -import { WorkplaceSearch } from './workplace_search'; import { renderApp, renderHeaderActions } from '.'; @@ -105,20 +103,6 @@ describe('renderApp', () => { }); expect(mockContainer.querySelector('.kbnPageTemplate')).not.toBeNull(); }); - - it('renders AppSearch', () => { - act(() => { - mount(AppSearch); - }); - expect(mockContainer.querySelector('.setupGuide')).not.toBeNull(); - }); - - it('renders WorkplaceSearch', () => { - act(() => { - mount(WorkplaceSearch); - }); - expect(mockContainer.querySelector('.setupGuide')).not.toBeNull(); - }); }); describe('renderHeaderActions', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 273bf1726cc51..369cee3eea646 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -25,19 +25,17 @@ import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { Router } from '@kbn/shared-ux-router'; import { DEFAULT_PRODUCT_FEATURES } from '../../common/constants'; -import { ClientConfigType, InitialAppData, ProductAccess } from '../../common/types'; +import { ClientConfigType, InitialAppData } from '../../common/types'; import { PluginsStart, ClientData, ESConfig, UpdateSideNavDefinitionFn } from '../plugin'; -import { externalUrl } from './shared/enterprise_search_url'; import { mountFlashMessagesLogic } from './shared/flash_messages'; -import { getCloudEnterpriseSearchHost } from './shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host'; import { mountHttpLogic } from './shared/http'; import { mountKibanaLogic } from './shared/kibana'; import { mountLicensingLogic } from './shared/licensing'; /** * This file serves as a reusable wrapper to share Kibana-level context and other helpers - * between various Enterprise Search plugins (e.g. AppSearch, WorkplaceSearch, ES landing page) + * between various Enterprise Search plugins (e.g. ES landing page, Search Applications, Behavior Analytics) * which should be imported and passed in as the first param in plugin.ts. */ @@ -58,19 +56,7 @@ export const renderApp = ( }, { config, data, esConfig }: { config: ClientConfigType; data: ClientData; esConfig: ESConfig } ) => { - const { - access, - appSearch, - configuredLimits, - enterpriseSearchVersion, - errorConnectingMessage, - features, - kibanaVersion, - publicUrl, - readOnlyMode, - searchOAuth, - workplaceSearch, - } = data; + const { features, kibanaVersion } = data; const { history } = params; const { application, chrome, http, notifications, uiSettings } = core; const { capabilities, navigateToUrl } = application; @@ -85,15 +71,6 @@ export const renderApp = ( ml, } = plugins; - const entCloudHost = getCloudEnterpriseSearchHost(plugins.cloud); - externalUrl.enterpriseSearchUrl = publicUrl || entCloudHost || config.host || ''; - - const noProductAccess: ProductAccess = { - hasAppSearchAccess: false, - hasWorkplaceSearchAccess: false, - }; - - const productAccess = access || noProductAccess; const productFeatures = features ?? { ...DEFAULT_PRODUCT_FEATURES }; const EmptyContext: FC> = ({ children }) => <>{children}; @@ -126,7 +103,6 @@ export const renderApp = ( lens, ml, navigateToUrl, - productAccess, productFeatures, renderHeaderActions: (HeaderActions) => params.setHeaderActionMenu( @@ -145,9 +121,7 @@ export const renderApp = ( license$: plugins.licensing?.license$ || of(undefined), }); const unmountHttpLogic = mountHttpLogic({ - errorConnectingMessage, http, - readOnlyMode, }); const unmountFlashMessagesLogic = mountFlashMessagesLogic({ notifications }); @@ -167,17 +141,7 @@ export const renderApp = ( - + diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx index 3d32130a50e51..dead5be119f6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/search_experiences_guide/search_experiences_guide.tsx @@ -61,7 +61,7 @@ export const SearchExperiencesGuide: React.FC = () => {

    @@ -179,34 +179,6 @@ export const SearchExperiencesGuide: React.FC = () => { target="_blank" />
    - - } - title="App Search" - description={i18n.translate( - 'xpack.enterpriseSearch.searchExperiences.guide.tutorials.appSearch.description', - { - defaultMessage: 'Build a search experience with App Search and Search UI.', - } - )} - href={SEARCH_EXPERIENCES_PLUGIN.APP_SEARCH_TUTORIAL_URL} - target="_blank" - /> - - - } - title="Workplace Search" - description={i18n.translate( - 'xpack.enterpriseSearch.searchExperiences.guide.tutorials.workplaceSearch.description', - { - defaultMessage: 'Build a search experience with Workplace Search and Search UI.', - } - )} - href={SEARCH_EXPERIENCES_PLUGIN.WORKPLACE_SEARCH_TUTORIAL_URL} - target="_blank" - /> -
    diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/search_experiences/jest.config.js index 1e39c00ae9893..198fc97d72872 100644 --- a/x-pack/plugins/enterprise_search/public/applications/search_experiences/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/search_experiences', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js index 7711d9b97523f..9f121241675bc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/semantic_search/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/semantic_search', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cypress/routes.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cypress/routes.ts index 3d74b81cfddb9..3e706ade8522c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/cypress/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cypress/routes.ts @@ -6,5 +6,3 @@ */ export const overviewPath = '/app/elasticsearch/overview'; -export const appSearchPath = '/app/enterprise_search/app_search'; -export const workplaceSearchPath = '/app/enterprise_search/workplace_search'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts index 6b98606649331..55ac838d6d9ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.test.ts @@ -19,9 +19,7 @@ describe('DocLinks', () => { docLinks.setDocLinks(links as any); - expect(docLinks.appSearchApis).toEqual(links.links.appSearch.apiRef); expect(docLinks.cloudIndexManagement).toEqual(links.links.cloud.indexManagement); expect(docLinks.enterpriseSearchConfig).toEqual(links.links.enterpriseSearch.configuration); - expect(docLinks.workplaceSearchZendesk).toEqual(links.links.workplaceSearch.zendesk); }); }); 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..86a62b6bc3219 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 @@ -12,31 +12,6 @@ class DocLinks { public aiSearchHelp: string; public apiKeyMetadata: string; public apiKeys: string; - public appSearchAdaptiveRelevance: string; - public appSearchApiClients: string; - public appSearchApiKeys: string; - public appSearchApis: string; - public appSearchAuthentication: string; - public appSearchCrawlRules: string; - public appSearchCurations: string; - public appSearchDuplicateDocuments: string; - public appSearchElasticsearchIndexedEngines: string; - public appSearchEntryPoints: string; - public appSearchGettingStarted: string; - public appSearchGuide: string; - public appSearchIndexingDocs: string; - public appSearchIndexingDocsSchema: string; - public appSearchLogSettings: string; - public appSearchMetaEngines: string; - public appSearchPrecision: string; - public appSearchRelevance: string; - public appSearchResultSettings: string; - public appSearchSearchUI: string; - public appSearchSecurity: string; - public appSearchSynonyms: string; - public appSearchWebCrawler: string; - public appSearchWebCrawlerEventLogs: string; - public appSearchWebCrawlerReference: string; public behavioralAnalytics: string; public behavioralAnalyticsCORS: string; public behavioralAnalyticsEvents: string; @@ -147,76 +122,12 @@ class DocLinks { public syncRulesAdvanced: string; public trainedModels: string; public textEmbedding: string; - public workplaceSearchApiKeys: string; - public workplaceSearchBox: string; - public workplaceSearchConfluenceCloud: string; - public workplaceSearchConfluenceCloudConnectorPackage: string; - public workplaceSearchConfluenceServer: string; - public workplaceSearchContentSources: string; - public workplaceSearchCustomConnectorPackage: string; - public workplaceSearchCustomSourcePermissions: string; - public workplaceSearchCustomSources: string; - public workplaceSearchDocumentPermissions: string; - public workplaceSearchDropbox: string; - public workplaceSearchExternalIdentities: string; - public workplaceSearchExternalSharePointOnline: string; - public workplaceSearchGatedFormBlog: string; - public workplaceSearchGatedFormDataUse: string; - public workplaceSearchGatedFormPrivacyStatement: string; - public workplaceSearchGatedFormTermsOfService: string; - public workplaceSearchGettingStarted: string; - public workplaceSearchGitHub: string; - public workplaceSearchGmail: string; - public workplaceSearchGoogleDrive: string; - public workplaceSearchIndexingSchedule: string; - public workplaceSearchJiraCloud: string; - public workplaceSearchJiraServer: string; - public workplaceSearchNetworkDrive: string; - public workplaceSearchOneDrive: string; - public workplaceSearchOutlook: string; - public workplaceSearchPermissions: string; - public workplaceSearchPrivateSourcePermissions: string; - public workplaceSearchSalesforce: string; - public workplaceSearchSecurity: string; - public workplaceSearchServiceNow: string; - public workplaceSearchSharePoint: string; - public workplaceSearchSharePointServer: string; - public workplaceSearchSlack: string; - public workplaceSearchSynch: string; - public workplaceSearchTeams: string; - public workplaceSearchZendesk: string; - public workplaceSearchZoom: string; constructor() { this.aiSearchDoc = ''; this.aiSearchHelp = ''; this.apiKeyMetadata = ''; this.apiKeys = ''; - this.appSearchAdaptiveRelevance = ''; - this.appSearchApis = ''; - this.appSearchApiClients = ''; - this.appSearchApiKeys = ''; - this.appSearchAuthentication = ''; - this.appSearchCrawlRules = ''; - this.appSearchCurations = ''; - this.appSearchDuplicateDocuments = ''; - this.appSearchEntryPoints = ''; - this.appSearchElasticsearchIndexedEngines = ''; - this.appSearchGettingStarted = ''; - this.appSearchGuide = ''; - this.appSearchIndexingDocs = ''; - this.appSearchIndexingDocsSchema = ''; - this.appSearchLogSettings = ''; - this.appSearchMetaEngines = ''; - this.appSearchPrecision = ''; - this.appSearchRelevance = ''; - this.appSearchResultSettings = ''; - this.appSearchSearchUI = ''; - this.appSearchSecurity = ''; - this.appSearchSynonyms = ''; - this.appSearchWebCrawler = ''; - this.appSearchWebCrawlerEventLogs = ''; - this.appSearchWebCrawlerReference = ''; this.behavioralAnalytics = ''; this.behavioralAnalyticsCORS = ''; this.behavioralAnalyticsEvents = ''; @@ -327,45 +238,6 @@ class DocLinks { this.syncRulesAdvanced = ''; this.trainedModels = ''; this.textEmbedding = ''; - this.workplaceSearchApiKeys = ''; - this.workplaceSearchBox = ''; - this.workplaceSearchConfluenceCloud = ''; - this.workplaceSearchConfluenceCloudConnectorPackage = ''; - this.workplaceSearchConfluenceServer = ''; - this.workplaceSearchContentSources = ''; - this.workplaceSearchCustomConnectorPackage = ''; - this.workplaceSearchCustomSources = ''; - this.workplaceSearchCustomSourcePermissions = ''; - this.workplaceSearchDocumentPermissions = ''; - this.workplaceSearchDropbox = ''; - this.workplaceSearchExternalSharePointOnline = ''; - this.workplaceSearchExternalIdentities = ''; - this.workplaceSearchGatedFormBlog = ''; - this.workplaceSearchGatedFormDataUse = ''; - this.workplaceSearchGatedFormPrivacyStatement = ''; - this.workplaceSearchGatedFormTermsOfService = ''; - this.workplaceSearchGettingStarted = ''; - this.workplaceSearchGitHub = ''; - this.workplaceSearchGmail = ''; - this.workplaceSearchGoogleDrive = ''; - this.workplaceSearchIndexingSchedule = ''; - this.workplaceSearchJiraCloud = ''; - this.workplaceSearchJiraServer = ''; - this.workplaceSearchNetworkDrive = ''; - this.workplaceSearchOneDrive = ''; - this.workplaceSearchOutlook = ''; - this.workplaceSearchPermissions = ''; - this.workplaceSearchPrivateSourcePermissions = ''; - this.workplaceSearchSalesforce = ''; - this.workplaceSearchSecurity = ''; - this.workplaceSearchServiceNow = ''; - this.workplaceSearchSharePoint = ''; - this.workplaceSearchSharePointServer = ''; - this.workplaceSearchSlack = ''; - this.workplaceSearchSynch = ''; - this.workplaceSearchTeams = ''; - this.workplaceSearchZendesk = ''; - this.workplaceSearchZoom = ''; } public setDocLinks(docLinks: DocLinksStart): void { @@ -373,32 +245,6 @@ class DocLinks { this.aiSearchHelp = docLinks.links.enterpriseSearch.aiSearchHelp; this.apiKeys = docLinks.links.enterpriseSearch.apiKeys; this.apiKeyMetadata = docLinks.links.security.mappingRoles; - this.appSearchAdaptiveRelevance = docLinks.links.appSearch.adaptiveRelevance; - this.appSearchApis = docLinks.links.appSearch.apiRef; - this.appSearchApiClients = docLinks.links.appSearch.apiClients; - this.appSearchApiKeys = docLinks.links.appSearch.apiKeys; - this.appSearchAuthentication = docLinks.links.appSearch.authentication; - this.appSearchCrawlRules = docLinks.links.appSearch.crawlRules; - this.appSearchCurations = docLinks.links.appSearch.curations; - this.appSearchDuplicateDocuments = docLinks.links.appSearch.duplicateDocuments; - this.appSearchElasticsearchIndexedEngines = - docLinks.links.appSearch.elasticsearchIndexedEngines; - this.appSearchEntryPoints = docLinks.links.appSearch.entryPoints; - this.appSearchGettingStarted = docLinks.links.appSearch.gettingStarted; - this.appSearchGuide = docLinks.links.appSearch.guide; - this.appSearchIndexingDocs = docLinks.links.appSearch.indexingDocuments; - this.appSearchIndexingDocsSchema = docLinks.links.appSearch.indexingDocumentsSchema; - this.appSearchLogSettings = docLinks.links.appSearch.logSettings; - this.appSearchMetaEngines = docLinks.links.appSearch.metaEngines; - this.appSearchPrecision = docLinks.links.appSearch.precisionTuning; - this.appSearchRelevance = docLinks.links.appSearch.relevanceTuning; - this.appSearchResultSettings = docLinks.links.appSearch.resultSettings; - this.appSearchSearchUI = docLinks.links.appSearch.searchUI; - this.appSearchSecurity = docLinks.links.appSearch.security; - this.appSearchSynonyms = docLinks.links.appSearch.synonyms; - this.appSearchWebCrawler = docLinks.links.appSearch.webCrawler; - this.appSearchWebCrawlerEventLogs = docLinks.links.appSearch.webCrawlerEventLogs; - this.appSearchWebCrawlerReference = docLinks.links.appSearch.webCrawlerReference; this.behavioralAnalytics = docLinks.links.enterpriseSearch.behavioralAnalytics; this.behavioralAnalyticsCORS = docLinks.links.enterpriseSearch.behavioralAnalyticsCORS; this.behavioralAnalyticsEvents = docLinks.links.enterpriseSearch.behavioralAnalyticsEvents; @@ -509,47 +355,6 @@ class DocLinks { this.syncRulesAdvanced = docLinks.links.enterpriseSearch.syncRulesAdvanced; this.trainedModels = docLinks.links.enterpriseSearch.trainedModels; this.textEmbedding = docLinks.links.enterpriseSearch.textEmbedding; - this.workplaceSearchGatedFormBlog = docLinks.links.workplaceSearch.gatedFormBlog; - this.workplaceSearchApiKeys = docLinks.links.workplaceSearch.apiKeys; - this.workplaceSearchBox = docLinks.links.workplaceSearch.box; - this.workplaceSearchConfluenceCloud = docLinks.links.workplaceSearch.confluenceCloud; - this.workplaceSearchConfluenceCloudConnectorPackage = - docLinks.links.workplaceSearch.confluenceCloudConnectorPackage; - this.workplaceSearchConfluenceServer = docLinks.links.workplaceSearch.confluenceServer; - this.workplaceSearchContentSources = docLinks.links.workplaceSearch.contentSources; - this.workplaceSearchCustomConnectorPackage = - docLinks.links.workplaceSearch.customConnectorPackage; - this.workplaceSearchCustomSources = docLinks.links.workplaceSearch.customSources; - this.workplaceSearchCustomSourcePermissions = - docLinks.links.workplaceSearch.customSourcePermissions; - this.workplaceSearchDocumentPermissions = docLinks.links.workplaceSearch.documentPermissions; - this.workplaceSearchDropbox = docLinks.links.workplaceSearch.dropbox; - this.workplaceSearchExternalSharePointOnline = - docLinks.links.workplaceSearch.externalSharePointOnline; - this.workplaceSearchExternalIdentities = docLinks.links.workplaceSearch.externalIdentities; - this.workplaceSearchGatedFormDataUse = docLinks.links.legal.dataUse; - this.workplaceSearchGatedFormPrivacyStatement = docLinks.links.legal.generalPrivacyStatement; - this.workplaceSearchGatedFormTermsOfService = docLinks.links.legal.termsOfService; - this.workplaceSearchGettingStarted = docLinks.links.workplaceSearch.gettingStarted; - this.workplaceSearchGitHub = docLinks.links.workplaceSearch.gitHub; - this.workplaceSearchGmail = docLinks.links.workplaceSearch.gmail; - this.workplaceSearchGoogleDrive = docLinks.links.workplaceSearch.googleDrive; - this.workplaceSearchIndexingSchedule = docLinks.links.workplaceSearch.indexingSchedule; - this.workplaceSearchJiraCloud = docLinks.links.workplaceSearch.jiraCloud; - this.workplaceSearchJiraServer = docLinks.links.workplaceSearch.jiraServer; - this.workplaceSearchNetworkDrive = docLinks.links.workplaceSearch.networkDrive; - this.workplaceSearchOneDrive = docLinks.links.workplaceSearch.oneDrive; - this.workplaceSearchPermissions = docLinks.links.workplaceSearch.permissions; - this.workplaceSearchPrivateSourcePermissions = - docLinks.links.workplaceSearch.privateSourcePermissions; - this.workplaceSearchSalesforce = docLinks.links.workplaceSearch.salesforce; - this.workplaceSearchSecurity = docLinks.links.workplaceSearch.security; - this.workplaceSearchServiceNow = docLinks.links.workplaceSearch.serviceNow; - this.workplaceSearchSharePoint = docLinks.links.workplaceSearch.sharePoint; - this.workplaceSearchSharePointServer = docLinks.links.workplaceSearch.sharePointServer; - this.workplaceSearchSlack = docLinks.links.workplaceSearch.slack; - this.workplaceSearchSynch = docLinks.links.workplaceSearch.synch; - this.workplaceSearchZendesk = docLinks.links.workplaceSearch.zendesk; } } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.ts index 01d5eb741cb01..f75c074b5533a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { externalUrl, getEnterpriseSearchUrl, getAppSearchUrl, getWorkplaceSearchUrl } from '.'; +import { externalUrl, getEnterpriseSearchUrl } from '.'; describe('Enterprise Search external URL helpers', () => { describe('getter/setter tests', () => { @@ -29,15 +29,5 @@ describe('Enterprise Search external URL helpers', () => { expect(getEnterpriseSearchUrl()).toEqual('http://localhost:3002'); expect(getEnterpriseSearchUrl('/login')).toEqual('http://localhost:3002/login'); }); - - it('generates a public App Search URL', () => { - expect(getAppSearchUrl()).toEqual('http://localhost:3002/as'); - expect(getAppSearchUrl('/path')).toEqual('http://localhost:3002/as/path'); - }); - - it('generates a public Workplace Search URL', () => { - expect(getWorkplaceSearchUrl()).toEqual('http://localhost:3002/ws'); - expect(getWorkplaceSearchUrl('/path')).toEqual('http://localhost:3002/ws/path'); - }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts index 6d322b8f652f1..461f9ef65257b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/external_url.ts @@ -30,9 +30,3 @@ export const externalUrl = { export const getEnterpriseSearchUrl = (path: string = ''): string => { return externalUrl.enterpriseSearchUrl + path; }; -export const getAppSearchUrl = (path: string = ''): string => { - return getEnterpriseSearchUrl('/as' + path); -}; -export const getWorkplaceSearchUrl = (path: string = ''): string => { - return getEnterpriseSearchUrl('/ws' + path); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts index bee5cadd493e6..eff7ff11d4555 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts @@ -5,9 +5,4 @@ * 2.0. */ -export { - externalUrl, - getEnterpriseSearchUrl, - getAppSearchUrl, - getWorkplaceSearchUrl, -} from './external_url'; +export { externalUrl, getEnterpriseSearchUrl } from './external_url'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host.test.ts deleted file mode 100644 index c2f14cfc5838e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { CloudSetup } from '@kbn/cloud-plugin/public'; - -import { getCloudEnterpriseSearchHost } from './get_cloud_enterprise_search_host'; - -const defaultPortCloud = { - cloudId: - 'gcp-cluster:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJDhhMDI4M2FmMDQxZjE5NWY3NzI5YmMwNGM2NmEwZmNlJDBjZDVjZDU2OGVlYmU1M2M4OWViN2NhZTViYWM4YjM3', - deploymentId: 'gcp-cluster', - isCloudEnabled: true, - cloudHost: 'us-central1.gcp.cloud.es.io', - cloudDefaultPort: '443', - registerCloudService: jest.fn(), - onboarding: {}, - isServerlessEnabled: false, - serverless: { - projectId: undefined, - }, - fetchElasticsearchConfig: jest.fn(), -} as CloudSetup; -// 9243 -const customPortCloud = { - cloudId: - 'custom-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=', - deploymentId: 'custom-port', - isCloudEnabled: true, - cloudHost: 'us-central1.gcp.cloud.es.io', - cloudDefaultPort: '9243', - registerCloudService: jest.fn(), - onboarding: {}, - isServerlessEnabled: false, - serverless: { - projectId: undefined, - }, - fetchElasticsearchConfig: jest.fn(), -} as CloudSetup; -const missingDeploymentIdCloud = { - cloudId: - 'dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=', - isCloudEnabled: true, - registerCloudService: jest.fn(), - onboarding: {}, - isServerlessEnabled: false, - serverless: { - projectId: undefined, - }, - fetchElasticsearchConfig: jest.fn(), -} as CloudSetup; -const noCloud = { - cloudId: undefined, - isCloudEnabled: false, - registerCloudService: jest.fn(), - onboarding: {}, - isServerlessEnabled: false, - serverless: { - projectId: undefined, - }, - fetchElasticsearchConfig: jest.fn(), -} as CloudSetup; - -describe('getCloudEnterpriseSearchHost', () => { - it('uses the default port', () => { - expect(getCloudEnterpriseSearchHost(defaultPortCloud)).toBe( - 'https://gcp-cluster.ent.us-central1.gcp.cloud.es.io' - ); - }); - - it('allows a custom port', () => { - expect(getCloudEnterpriseSearchHost(customPortCloud)).toBe( - 'https://custom-port.ent.us-central1.gcp.cloud.es.io:9243' - ); - }); - - it('is undefined when there is no deployment id', () => { - expect(getCloudEnterpriseSearchHost(missingDeploymentIdCloud)).toBe(undefined); - }); - - it('is undefined with an undefined cloud id', () => { - expect(getCloudEnterpriseSearchHost(noCloud)).toBe(undefined); - }); - - it('is undefined when cloud is undefined', () => { - expect(getCloudEnterpriseSearchHost(undefined)).toBe(undefined); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host.ts b/x-pack/plugins/enterprise_search/public/applications/shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host.ts deleted file mode 100644 index 01e8ae84be194..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/get_cloud_enterprise_search_host/get_cloud_enterprise_search_host.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 type { CloudSetup } from '@kbn/cloud-plugin/public'; - -export function getCloudEnterpriseSearchHost(cloud: CloudSetup | undefined): string | undefined { - if (cloud && cloud.isCloudEnabled && cloud.cloudId && cloud.deploymentId && cloud.cloudHost) { - // Enterprise Search Server url are formed like this `https://.ent. - return `https://${cloud.deploymentId}.ent.${cloud.cloudHost}${ - cloud.cloudDefaultPort && cloud.cloudDefaultPort !== '443' ? `:${cloud.cloudDefaultPort}` : '' - }`; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts index 1d4372ca5b734..8ea5fd4e2b090 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts @@ -26,8 +26,6 @@ describe('HttpLogic', () => { expect(HttpLogic.values).toEqual({ http: mockHttp, httpInterceptors: expect.any(Array), - errorConnectingMessage: '', - readOnlyMode: false, }); }); @@ -35,169 +33,16 @@ describe('HttpLogic', () => { it('sets values from props', () => { mountHttpLogic({ http: mockHttp, - errorConnectingMessage: '502 Bad Gateway', - readOnlyMode: true, }); expect(HttpLogic.values).toEqual({ http: mockHttp, httpInterceptors: expect.any(Array), - errorConnectingMessage: '502 Bad Gateway', - readOnlyMode: true, }); }); }); - describe('onConnectionError', () => { - it('sets the error connecting flag and related message ', () => { - mount({ - errorConnectingMessage: '', - }); - - HttpLogic.actions.onConnectionError('500 Error'); - expect(HttpLogic.values.errorConnectingMessage).toEqual('500 Error'); - }); - }); - - describe('setReadOnlyMode()', () => { - it('sets readOnlyMode value', () => { - mount(); - expect(HttpLogic.values.readOnlyMode).toEqual(false); - - HttpLogic.actions.setReadOnlyMode(true); - expect(HttpLogic.values.readOnlyMode).toEqual(true); - - HttpLogic.actions.setReadOnlyMode(false); - expect(HttpLogic.values.readOnlyMode).toEqual(false); - }); - }); - describe('http interceptors', () => { - describe('initializeHttpInterceptors()', () => { - beforeEach(() => { - mount(); - jest.spyOn(HttpLogic.actions, 'setHttpInterceptors'); - }); - - it('calls http.intercept and sets an array of interceptors', () => { - mockHttp.intercept - .mockImplementationOnce(() => 'removeErrorInterceptorFn' as any) - .mockImplementationOnce(() => 'removeReadOnlyInterceptorFn' as any); - HttpLogic.actions.initializeHttpInterceptors(); - - expect(mockHttp.intercept).toHaveBeenCalled(); - expect(HttpLogic.actions.setHttpInterceptors).toHaveBeenCalledWith([ - 'removeErrorInterceptorFn', - 'removeReadOnlyInterceptorFn', - ]); - }); - - describe('errorConnectingInterceptor', () => { - let interceptedResponse: any; - - beforeEach(() => { - interceptedResponse = mockHttp.intercept.mock.calls[0][0].responseError; - jest.spyOn(HttpLogic.actions, 'onConnectionError'); - }); - - it('sets the connection error message if the response header is true', async () => { - const httpResponse = { - response: { - url: '/internal/app_search/engines', - headers: { get: () => 'true' }, - status: 500, - statusText: 'Error', - }, - }; - await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); - - expect(HttpLogic.actions.onConnectionError).toHaveBeenCalledWith('500 Error'); - }); - - it('takes no action if the response header is false', async () => { - const httpResponse = { - response: { - url: '/internal/app_search/engines', - headers: { get: () => 'false' }, - }, - }; - await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); - - expect(HttpLogic.actions.onConnectionError).not.toHaveBeenCalled(); - }); - - describe('isEnterpriseSearchApi check', () => { - let httpResponse: any; - - afterEach(async () => { - // Should always re-reject the promise and not call setErrorConnecting - await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); - expect(HttpLogic.actions.onConnectionError).not.toHaveBeenCalled(); - }); - - it('does not handle non-Enterprise Search API calls', async () => { - httpResponse = { - response: { url: '/api/some_other_plugin/', headers: { get: () => 'true' } }, - }; - }); - - it('does not handle invalid responses', async () => { - httpResponse = {}; - }); - }); - }); - - describe('readOnlyModeInterceptor', () => { - let interceptedResponse: any; - - beforeEach(() => { - interceptedResponse = mockHttp.intercept.mock.calls[1][0].response; - jest.spyOn(HttpLogic.actions, 'setReadOnlyMode'); - }); - - it('sets readOnlyMode to true if the response header is true', async () => { - const httpResponse = { - response: { url: '/internal/app_search/engines', headers: { get: () => 'true' } }, - }; - await expect(interceptedResponse(httpResponse)).resolves.toEqual(httpResponse); - - expect(HttpLogic.actions.setReadOnlyMode).toHaveBeenCalledWith(true); - }); - - it('sets readOnlyMode to false if the response header is false', async () => { - const httpResponse = { - response: { - url: '/internal/workplace_search/overview', - headers: { get: () => 'false' }, - }, - }; - await expect(interceptedResponse(httpResponse)).resolves.toEqual(httpResponse); - - expect(HttpLogic.actions.setReadOnlyMode).toHaveBeenCalledWith(false); - }); - - describe('isEnterpriseSearchApi check', () => { - let httpResponse: any; - - afterEach(async () => { - // Should always resolve the promise and not call setReadOnlyMode - await expect(interceptedResponse(httpResponse)).resolves.toEqual(httpResponse); - expect(HttpLogic.actions.setReadOnlyMode).not.toHaveBeenCalled(); - }); - - it('does not handle non-Enterprise Search API calls', async () => { - httpResponse = { - response: { url: '/api/some_other_plugin/', headers: { get: () => 'true' } }, - }; - }); - - it('does not handle invalid responses', async () => { - httpResponse = {}; - }); - }); - }); - }); - it('sets httpInterceptors and calls all valid remove functions on unmount', () => { const unmount = mount(); const httpInterceptors = [jest.fn(), undefined, jest.fn()] as any; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index d69c4211f88ef..8b05c9da34513 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -7,30 +7,23 @@ import { kea, MakeLogicType } from 'kea'; -import { HttpInterceptorResponseError, HttpResponse, HttpSetup } from '@kbn/core/public'; - -import { ERROR_CONNECTING_HEADER, READ_ONLY_MODE_HEADER } from '../../../../common/constants'; +import { HttpSetup } from '@kbn/core/public'; export interface HttpValues { - errorConnectingMessage: string; http: HttpSetup; httpInterceptors: Function[]; - readOnlyMode: boolean; } interface HttpActions { initializeHttpInterceptors(): void; onConnectionError(errorConnectingMessage: string): { errorConnectingMessage: string }; setHttpInterceptors(httpInterceptors: Function[]): { httpInterceptors: Function[] }; - setReadOnlyMode(readOnlyMode: boolean): { readOnlyMode: boolean }; } export const HttpLogic = kea>({ actions: { initializeHttpInterceptors: () => null, - onConnectionError: (errorConnectingMessage) => ({ errorConnectingMessage }), setHttpInterceptors: (httpInterceptors) => ({ httpInterceptors }), - setReadOnlyMode: (readOnlyMode) => ({ readOnlyMode }), }, events: ({ values, actions }) => ({ afterMount: () => { @@ -42,68 +35,22 @@ export const HttpLogic = kea>({ }); }, }), - listeners: ({ values, actions }) => ({ + listeners: ({ actions }) => ({ initializeHttpInterceptors: () => { - const httpInterceptors = []; - - const errorConnectingInterceptor = values.http.intercept({ - responseError: async (httpResponse) => { - if (isEnterpriseSearchApi(httpResponse)) { - const hasErrorConnecting = httpResponse.response!.headers.get(ERROR_CONNECTING_HEADER); - if (hasErrorConnecting === 'true') { - const { status, statusText } = httpResponse.response!; - actions.onConnectionError(`${status} ${statusText}`); - } - } - - // Re-throw error so that downstream catches work as expected - return Promise.reject(httpResponse) as Promise; - }, - }); - httpInterceptors.push(errorConnectingInterceptor); - - const readOnlyModeInterceptor = values.http.intercept({ - response: async (httpResponse) => { - if (isEnterpriseSearchApi(httpResponse)) { - const readOnlyMode = httpResponse.response!.headers.get(READ_ONLY_MODE_HEADER); - - if (readOnlyMode === 'true') { - actions.setReadOnlyMode(true); - } else { - actions.setReadOnlyMode(false); - } - } - - return Promise.resolve(httpResponse); - }, - }); - httpInterceptors.push(readOnlyModeInterceptor); - + const httpInterceptors: Function[] = []; actions.setHttpInterceptors(httpInterceptors); }, }), path: ['enterprise_search', 'http_logic'], reducers: ({ props }) => ({ - errorConnectingMessage: [ - props.errorConnectingMessage || '', - { - // @ts-expect-error upgrade typescript v5.1.6 - onConnectionError: (_, { errorConnectingMessage }) => errorConnectingMessage, - }, - ], http: [props.http, {}], httpInterceptors: [ [], { - // @ts-expect-error upgrade typescript v5.1.6 - setHttpInterceptors: (_, { httpInterceptors }) => httpInterceptors, - }, - ], - readOnlyMode: [ - props.readOnlyMode || false, - { - // @ts-expect-error upgrade typescript v5.1.6 - setReadOnlyMode: (_, { readOnlyMode }) => readOnlyMode, + setHttpInterceptors: ( + _: Function[], + { httpInterceptors }: { httpInterceptors: Function[] } + ) => httpInterceptors, }, ], }), @@ -113,22 +60,10 @@ export const HttpLogic = kea>({ * Mount/props helper */ interface HttpLogicProps { - errorConnectingMessage?: string; http: HttpSetup; - readOnlyMode?: boolean; } export const mountHttpLogic = (props: HttpLogicProps) => { HttpLogic(props); return HttpLogic.mount(); }; - -/** - * Small helper that checks whether or not an http call is for an Enterprise Search API - */ -const isEnterpriseSearchApi = (httpResponse: HttpResponse) => { - if (!httpResponse.response) return false; // Typically this means Kibana has stopped working, in which case we short-circuit early to prevent errors - - const { url } = httpResponse.response; - return url.includes('/internal/app_search/') || url.includes('/internal/workplace_search/'); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/shared/jest.config.js index 5ee13cc30aeaf..5a71ac7bdf101 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/jest.config.js +++ b/x-pack/plugins/enterprise_search/public/applications/shared/jest.config.js @@ -19,8 +19,4 @@ module.exports = { ], coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/shared', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 2185f0df344cc..d4e1dfed7e236 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -32,7 +32,7 @@ import { ConnectorDefinition } from '@kbn/search-connectors'; import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public'; import { SharePluginStart } from '@kbn/share-plugin/public'; -import { ClientConfigType, ProductAccess, ProductFeatures } from '../../../../common/types'; +import { ClientConfigType, ProductFeatures } from '../../../../common/types'; import { ESConfig, UpdateSideNavDefinitionFn } from '../../../plugin'; import { HttpLogic } from '../http'; @@ -62,7 +62,6 @@ export interface KibanaLogicProps { lens?: LensPublicStart; ml?: MlPluginStart; navigateToUrl: RequiredFieldsOnly; - productAccess: ProductAccess; productFeatures: ProductFeatures; renderHeaderActions(HeaderActions?: FC): void; security?: SecurityPluginStart; @@ -95,7 +94,6 @@ export interface KibanaValues { lens: LensPublicStart | null; ml: MlPluginStart | null; navigateToUrl(path: string, options?: CreateHrefOptions): Promise; - productAccess: ProductAccess; productFeatures: ProductFeatures; renderHeaderActions(HeaderActions?: FC): void; security: SecurityPluginStart | null; @@ -140,7 +138,6 @@ export const KibanaLogic = kea>({ }, {}, ], - productAccess: [props.productAccess, {}], productFeatures: [props.productFeatures, {}], renderHeaderActions: [props.renderHeaderActions, {}], security: [props.security || null, {}], diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts index 7cda1c6893b17..c6d86dc385b23 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts @@ -19,9 +19,7 @@ import { useGenerateBreadcrumbs, useEuiBreadcrumbs, useEnterpriseSearchBreadcrumbs, - useAppSearchBreadcrumbs, useSearchBreadcrumbs, - useWorkplaceSearchBreadcrumbs, } from './generate_breadcrumbs'; describe('useGenerateBreadcrumbs', () => { @@ -228,115 +226,3 @@ describe('useEnterpriseSearchBreadcrumbs', () => { ]); }); }); - -describe('useAppSearchBreadcrumbs', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockHistory.createHref.mockImplementation( - ({ pathname }: any) => `/app/enterprise_search/app_search${pathname}` - ); - }); - - it('Builds a chain of breadcrumbs with Enterprise Search and App Search at the root', () => { - const breadcrumbs = [ - { - text: 'Page 1', - path: '/page1', - }, - { - text: 'Page 2', - path: '/page2', - }, - ]; - - expect(useAppSearchBreadcrumbs(breadcrumbs)).toEqual([ - { - text: 'Enterprise Search', - href: '/app/elasticsearch/overview', - onClick: expect.any(Function), - }, - { - text: 'App Search', - href: '/app/enterprise_search/app_search/', - onClick: expect.any(Function), - }, - { - text: 'Page 1', - href: '/app/enterprise_search/app_search/page1', - onClick: expect.any(Function), - }, - { - text: 'Page 2', - }, - ]); - }); - - it('shows just the root if breadcrumbs is empty', () => { - expect(useAppSearchBreadcrumbs()).toEqual([ - { - text: 'Enterprise Search', - href: '/app/elasticsearch/overview', - onClick: expect.any(Function), - }, - { - text: 'App Search', - }, - ]); - }); -}); - -describe('useWorkplaceSearchBreadcrumbs', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockHistory.createHref.mockImplementation( - ({ pathname }: any) => `/app/enterprise_search/workplace_search${pathname}` - ); - }); - - it('Builds a chain of breadcrumbs with Enterprise Search and Workplace Search at the root', () => { - const breadcrumbs = [ - { - text: 'Page 1', - path: '/page1', - }, - { - text: 'Page 2', - path: '/page2', - }, - ]; - - expect(useWorkplaceSearchBreadcrumbs(breadcrumbs)).toEqual([ - { - text: 'Enterprise Search', - href: '/app/elasticsearch/overview', - onClick: expect.any(Function), - }, - { - text: 'Workplace Search', - href: '/app/enterprise_search/workplace_search/', - onClick: expect.any(Function), - }, - { - text: 'Page 1', - href: '/app/enterprise_search/workplace_search/page1', - onClick: expect.any(Function), - }, - { - text: 'Page 2', - }, - ]); - }); - - it('shows just the root if breadcrumbs is empty', () => { - expect(useWorkplaceSearchBreadcrumbs()).toEqual([ - { - text: 'Enterprise Search', - href: '/app/elasticsearch/overview', - onClick: expect.any(Function), - }, - { - text: 'Workplace Search', - }, - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index 024a84858f108..a8765af733550 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -12,14 +12,12 @@ import { i18n } from '@kbn/i18n'; import { ANALYTICS_PLUGIN, - APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN, ENTERPRISE_SEARCH_PRODUCT_NAME, AI_SEARCH_PLUGIN, SEARCH_EXPERIENCES_PLUGIN, SEARCH_PRODUCT_NAME, VECTOR_SEARCH_PLUGIN, - WORKPLACE_SEARCH_PLUGIN, SEMANTIC_SEARCH_PLUGIN, APPLICATIONS_PLUGIN, GETTING_STARTED_TITLE, @@ -144,15 +142,6 @@ export const useElasticsearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => ...breadcrumbs, ]); -export const useAppSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => - useEnterpriseSearchBreadcrumbs([{ text: APP_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs]); - -export const useWorkplaceSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => - useEnterpriseSearchBreadcrumbs([ - { text: WORKPLACE_SEARCH_PLUGIN.NAME, path: '/' }, - ...breadcrumbs, - ]); - export const useEnterpriseSearchContentBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useSearchBreadcrumbs([ { text: ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAV_TITLE, path: '/' }, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts index 5db04cc0a15c2..b2f175cfc9f29 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { generateTitle, searchTitle, appSearchTitle, workplaceSearchTitle } from './generate_title'; +import { generateTitle, searchTitle } from './generate_title'; describe('generateTitle', () => { it('creates a hyphen separated string from an array of page titles', () => { @@ -20,37 +20,8 @@ describe('searchTitle', () => { expect(title).toEqual('Setup Guide - Elasticsearch'); }); - it('can be mixed and matched', () => { - const title = searchTitle([appSearchTitle(['Some Page'])]); - expect(title).toEqual('Some Page - App Search - Elasticsearch'); - }); - it('falls back to product name', () => { const title = searchTitle(); expect(title).toEqual('Elasticsearch'); }); }); - -describe('appSearchTitle', () => { - it('automatically appends the App Search product onto the pages array', () => { - const title = appSearchTitle(['Engines']); - expect(title).toEqual('Engines - App Search'); - }); - - it('falls back to product name', () => { - const title = appSearchTitle(); - expect(title).toEqual('App Search'); - }); -}); - -describe('workplaceSearchTitle', () => { - it('automatically appends the Workplace Search product onto the pages array', () => { - const title = workplaceSearchTitle(['Sources']); - expect(title).toEqual('Sources - Workplace Search'); - }); - - it('falls back to product name', () => { - const title = workplaceSearchTitle(); - expect(title).toEqual('Workplace Search'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index df7d16cddc4d4..42bc9be087bd2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -10,13 +10,11 @@ import { i18n } from '@kbn/i18n'; import { AI_SEARCH_PLUGIN, ANALYTICS_PLUGIN, - APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN, SEARCH_EXPERIENCES_PLUGIN, SEARCH_PRODUCT_NAME, SEMANTIC_SEARCH_PLUGIN, VECTOR_SEARCH_PLUGIN, - WORKPLACE_SEARCH_PLUGIN, } from '../../../../common/constants'; /** @@ -49,12 +47,6 @@ export const elasticsearchTitle = (page: Title = []) => }), ]); -export const appSearchTitle = (page: Title = []) => - generateTitle([...page, APP_SEARCH_PLUGIN.NAME]); - -export const workplaceSearchTitle = (page: Title = []) => - generateTitle([...page, WORKPLACE_SEARCH_PLUGIN.NAME]); - export const searchExperiencesTitle = (page: Title = []) => generateTitle([...page, SEARCH_EXPERIENCES_PLUGIN.NAME]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts index 2da72eb28b6e9..a77acdbba6177 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts @@ -11,8 +11,6 @@ export { SetEnterpriseSearchContentChrome, SetElasticsearchChrome, SetAiSearchChrome, - SetAppSearchChrome, - SetWorkplaceSearchChrome, SetSearchExperiencesChrome, SetEnterpriseSearchApplicationsChrome, SetSemanticSearchChrome, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.test.tsx index 9fd2799c81330..264b7bd24b7e4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.test.tsx @@ -16,28 +16,17 @@ import { shallow } from 'enzyme'; jest.mock('./generate_breadcrumbs', () => ({ useGenerateBreadcrumbs: jest.requireActual('./generate_breadcrumbs').useGenerateBreadcrumbs, useSearchBreadcrumbs: jest.fn(() => (crumbs: any) => crumbs), - useAppSearchBreadcrumbs: jest.fn(() => (crumbs: any) => crumbs), - useWorkplaceSearchBreadcrumbs: jest.fn(() => (crumbs: any) => crumbs), })); -import { - useSearchBreadcrumbs, - useAppSearchBreadcrumbs, - useWorkplaceSearchBreadcrumbs, -} from './generate_breadcrumbs'; +import { useSearchBreadcrumbs } from './generate_breadcrumbs'; jest.mock('./generate_title', () => ({ searchTitle: jest.fn((title: any) => title), - appSearchTitle: jest.fn((title: any) => title), - workplaceSearchTitle: jest.fn((title: any) => title), })); -import { searchTitle, appSearchTitle, workplaceSearchTitle } from './generate_title'; +import { searchTitle } from './generate_title'; -import { SetSearchChrome, SetAppSearchChrome, SetWorkplaceSearchChrome } from '.'; +import { SetSearchChrome } from '.'; describe('Set Kibana Chrome helpers', () => { - const mockCurrentPath = (pathname: string) => - setMockValues({ history: { location: { pathname } } }); - beforeEach(() => { jest.clearAllMocks(); setMockValues({ history: mockHistory }); @@ -68,52 +57,4 @@ describe('Set Kibana Chrome helpers', () => { expect(useSearchBreadcrumbs).toHaveBeenCalledWith([]); }); }); - - describe('SetAppSearchChrome', () => { - it('sets breadcrumbs and document title', () => { - mockCurrentPath('/engines/{name}/curations'); - shallow(); - - expect(appSearchTitle).toHaveBeenCalledWith(['Curations', 'Some Engine', 'Engines']); - expect(useAppSearchBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Engines', path: '/engines' }, - { text: 'Some Engine', path: '/engines/{name}' }, - { text: 'Curations', path: '/engines/{name}/curations' }, - ]); - }); - - it('handles empty trails as a root-level page', () => { - shallow(); - - expect(appSearchTitle).toHaveBeenCalledWith([]); - expect(useAppSearchBreadcrumbs).toHaveBeenCalledWith([]); - }); - }); - - describe('SetWorkplaceSearchChrome', () => { - it('sets breadcrumbs and document title', () => { - mockCurrentPath('/groups/{id}/source_prioritization'); - shallow( - - ); - - expect(workplaceSearchTitle).toHaveBeenCalledWith([ - 'Source Prioritization', - 'Some Group', - 'Groups', - ]); - expect(useWorkplaceSearchBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Groups', path: '/groups' }, - { text: 'Some Group', path: '/groups/{id}' }, - { text: 'Source Prioritization', path: '/groups/{id}/source_prioritization' }, - ]); - }); - - it('handles empty trails as a root-level page', () => { - shallow(); - - expect(workplaceSearchTitle).toHaveBeenCalledWith([]); - expect(useWorkplaceSearchBreadcrumbs).toHaveBeenCalledWith([]); - }); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index c556213ec8b42..c32b4bba018f3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -20,8 +20,6 @@ import { useEnterpriseSearchContentBreadcrumbs, useAiSearchBreadcrumbs, useElasticsearchBreadcrumbs, - useAppSearchBreadcrumbs, - useWorkplaceSearchBreadcrumbs, BreadcrumbTrail, useSearchExperiencesBreadcrumbs, useVectorSearchBreadcrumbs, @@ -30,7 +28,6 @@ import { import { aiSearchTitle, analyticsTitle, - appSearchTitle, elasticsearchTitle, enterpriseSearchContentTitle, generateTitle, @@ -39,24 +36,8 @@ import { searchTitle, semanticSearchTitle, vectorSearchTitle, - workplaceSearchTitle, } from './generate_title'; -/** - * Helpers for setting Kibana chrome (breadcrumbs, doc titles) on React view mount - * @see https://github.com/elastic/kibana/blob/main/src/core/public/chrome/chrome_service.tsx - * - * Example usage (don't forget to i18n.translate() page titles!): - * - * - * Breadcrumb output: Enterprise Search > App Search > Engines > Example Engine Name > Curations - * Title output: Curations - Example Engine Name - Engines - App Search - Elastic - * - * - * Breadcrumb output: Enterprise Search > Workplace Search - * Title output: Workplace Search - Elastic - */ - interface SetChromeProps { trail?: BreadcrumbTrail; } @@ -112,23 +93,6 @@ export const SetElasticsearchChrome: React.FC = ({ trail = [] }) return null; }; -export const SetAppSearchChrome: React.FC = ({ trail = [] }) => { - const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); - - const title = reverseArray(trail); - const docTitle = appSearchTitle(title); - - const crumbs = useGenerateBreadcrumbs(trail); - const breadcrumbs = useAppSearchBreadcrumbs(crumbs); - - useEffect(() => { - setBreadcrumbs(breadcrumbs); - setDocTitle(docTitle); - }, [trail]); - - return null; -}; - export const SetAiSearchChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); @@ -146,23 +110,6 @@ export const SetAiSearchChrome: React.FC = ({ trail = [] }) => { return null; }; -export const SetWorkplaceSearchChrome: React.FC = ({ trail = [] }) => { - const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); - - const title = reverseArray(trail); - const docTitle = workplaceSearchTitle(title); - - const crumbs = useGenerateBreadcrumbs(trail); - const breadcrumbs = useWorkplaceSearchBreadcrumbs(crumbs); - - useEffect(() => { - setBreadcrumbs(breadcrumbs); - setDocTitle(docTitle); - }, [trail]); - - return null; -}; - export const SetEnterpriseSearchContentChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 19a0fea68969b..3296a9b82e977 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -20,7 +20,6 @@ import { renderHook } from '@testing-library/react'; import { EuiSideNavItemType } from '@elastic/eui'; import { DEFAULT_PRODUCT_FEATURES } from '../../../../common/constants'; -import { ProductAccess } from '../../../../common/types'; import { useEnterpriseSearchNav, @@ -28,10 +27,6 @@ import { useEnterpriseSearchAnalyticsNav, } from './nav'; -const DEFAULT_PRODUCT_ACCESS: ProductAccess = { - hasAppSearchAccess: true, - hasWorkplaceSearchAccess: true, -}; const baseNavItems = [ expect.objectContaining({ 'data-test-subj': 'searchSideNav-Home', @@ -186,16 +181,6 @@ const mockNavLinks = [ title: 'Inference Endpoints', url: '/app/elasticsearch/relevance/inference_endpoints', }, - { - id: 'appSearch:engines', - title: 'App Search', - url: '/app/enterprise_search/app_search', - }, - { - id: 'workplaceSearch', - title: 'Workplace Search', - url: '/app/enterprise_search/workplace_search', - }, { id: 'enterpriseSearchElasticsearch', title: 'Elasticsearch', @@ -221,7 +206,6 @@ const mockNavLinks = [ const defaultMockValues = { hasEnterpriseLicense: true, isSidebarEnabled: true, - productAccess: DEFAULT_PRODUCT_ACCESS, productFeatures: DEFAULT_PRODUCT_FEATURES, }; @@ -233,10 +217,8 @@ describe('useEnterpriseSearchContentNav', () => { }); it('returns an array of top-level Enterprise Search nav items', () => { - const fullProductAccess: ProductAccess = DEFAULT_PRODUCT_ACCESS; setMockValues({ ...defaultMockValues, - productAccess: fullProductAccess, }); const { result } = renderHook(() => useEnterpriseSearchNav()); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx index 370972ebe53ea..e0ffe68d3207f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx @@ -11,7 +11,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; @@ -127,22 +126,6 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { }); }); - describe('read-only mode', () => { - it('renders a callout if in read-only mode', () => { - setMockValues({ readOnlyMode: true }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut).exists()).toBe(true); - }); - - it('does not render a callout if not in read-only mode', () => { - setMockValues({ readOnlyMode: false }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCallOut).exists()).toBe(false); - }); - }); - describe('flash messages', () => { it('renders FlashMessages by default', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index bc1f6f13a0bb1..983b4c3f5ce88 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -10,14 +10,9 @@ import React, { useLayoutEffect } from 'react'; import classNames from 'classnames'; import { useValues } from 'kea'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - import { KibanaPageTemplate, KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; import { FlashMessages } from '../flash_messages'; -import { HttpLogic } from '../http'; import { KibanaLogic } from '../kibana'; import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs'; import { Loading } from '../loading'; @@ -65,7 +60,6 @@ export const EnterpriseSearchPageTemplateWrapper: React.FC = hideEmbeddedConsole = false, ...pageTemplateProps }) => { - const { readOnlyMode } = useValues(HttpLogic); const { renderHeaderActions, consolePlugin } = useValues(KibanaLogic); const hasCustomEmptyState = !!emptyState; @@ -96,19 +90,6 @@ export const EnterpriseSearchPageTemplateWrapper: React.FC = solutionNav={solutionNav && solutionNav.items ? { icon: navIcon, ...solutionNav } : undefined} > {setPageChrome} - {readOnlyMode && ( - <> - - - - )} {!hideFlashMessages && } {isLoading ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts deleted file mode 100644 index 6d9365d63c320..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/elasticsearch_users.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 const elasticsearchUsers = [ - { - email: 'user1@user.com', - username: 'user1', - enabled: true, - }, -]; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts deleted file mode 100644 index 486c1ba6c9af6..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/__mocks__/roles.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { engines } from '../../../app_search/__mocks__/engines.mock'; - -import { AttributeName } from '../../types'; - -import { elasticsearchUsers } from './elasticsearch_users'; - -export const asRoleMapping = { - id: 'sdgfasdgadf123', - attributeName: 'role' as AttributeName, - attributeValue: 'superuser', - authProvider: ['*'], - roleType: 'owner', - rules: { - role: 'superuser', - }, - accessAllEngines: true, - engines, - toolTip: { - content: 'Elasticsearch superusers will always be able to log in as the owner', - }, -}; - -export const wsRoleMapping = { - id: '602d4ba85foobarbaz123', - attributeName: 'username' as AttributeName, - attributeValue: 'user', - authProvider: ['*', 'other_auth'], - roleType: 'admin', - rules: { - username: 'user', - }, - allGroups: true, - groups: [ - { - id: '602c3b475foobarbaz123', - name: 'Default', - createdAt: '2021-02-16T21:38:15Z', - updatedAt: '2021-02-16T21:40:32Z', - contentSources: [ - { - id: '602c3bcf5foobarbaz123', - name: 'National Parks', - serviceType: 'custom', - }, - ], - users: [ - { - id: '602c3b485foobarbaz123', - name: 'you_know_for_search', - email: 'foo@example.com', - initials: 'E', - pictureUrl: null, - color: '#ffcc13', - }, - { - id: '602c3bf85foobarbaz123', - name: 'elastic', - email: null, - initials: 'E', - pictureUrl: null, - color: '#7968ff', - }, - ], - usersCount: 2, - }, - ], -}; - -export const invitation = { - email: 'foo@example.com', - code: '123fooqwe', -}; - -export const wsSingleUserRoleMapping = { - invitation, - elasticsearchUser: elasticsearchUsers[0], - roleMapping: wsRoleMapping, -}; - -export const asSingleUserRoleMapping = { - invitation, - elasticsearchUser: elasticsearchUsers[0], - roleMapping: asRoleMapping, -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx deleted file mode 100644 index c96f2a7bfb832..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 { EuiFieldText, EuiFormRow } from '@elastic/eui'; - -import { AttributeName } from '../types'; - -import { AttributeSelector } from './attribute_selector'; -import { REQUIRED_LABEL } from './constants'; - -const handleAttributeSelectorChange = jest.fn(); -const handleAttributeValueChange = jest.fn(); - -const baseProps = { - attributeName: 'username' as AttributeName, - attributeValue: 'Something', - attributeValueInvalid: false, - attributes: ['a', 'b', 'c'], - elasticsearchRoles: ['whatever'], - disabled: false, - handleAttributeSelectorChange, - handleAttributeValueChange, -}; - -describe('AttributeSelector', () => { - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="AttributeSelector"]').exists()).toBe(true); - }); - - describe('Form controls', () => { - it('handles fallback props', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiFieldText).prop('value')).toEqual(''); - }); - - it('should call the "handleAttributeSelectorChange" prop when a value is selected', () => { - const wrapper = shallow(); - const select = wrapper.find('[data-test-subj="ExternalAttributeSelect"]'); - const event = { target: { value: 'kbn_saml' } }; - select.simulate('change', event); - - expect(handleAttributeSelectorChange).toHaveBeenCalledWith( - 'kbn_saml', - baseProps.elasticsearchRoles[0] - ); - }); - - it('should call the "handleAttributeSelectorChange" prop when field text value is changed', () => { - const wrapper = shallow(); - const input = wrapper.find(EuiFieldText); - const event = { target: { value: 'kbn_saml' } }; - input.simulate('change', event); - - expect(handleAttributeSelectorChange).toHaveBeenCalledWith( - 'kbn_saml', - baseProps.elasticsearchRoles[0] - ); - }); - - it('should call the "handleAttributeSelectorChange" prop when attribute value is selected', () => { - const wrapper = shallow(); - const select = wrapper.find('[data-test-subj="ElasticsearchRoleSelect"]'); - const event = { target: { value: 'kbn_saml' } }; - select.simulate('change', event); - - expect(handleAttributeSelectorChange).toHaveBeenCalledWith( - 'kbn_saml', - baseProps.elasticsearchRoles[0] - ); - }); - - it('shows helpText when attributeValueInvalid', () => { - const wrapper = shallow(); - const formRow = wrapper.find(EuiFormRow).at(1); - - expect(formRow.prop('helpText')).toEqual(REQUIRED_LABEL); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx deleted file mode 100644 index d2c47e2d7b348..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 { EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; - -import { AttributeName, AttributeExamples } from '../types'; - -import { ATTRIBUTE_VALUE_LABEL, REQUIRED_LABEL, EXTERNAL_ATTRIBUTE_LABEL } from './constants'; - -interface Props { - attributeName: AttributeName; - attributeValue?: string; - attributeValueInvalid: boolean; - attributes: string[]; - elasticsearchRoles: string[]; - disabled?: boolean; - handleAttributeSelectorChange(value: string, elasticsearchRole: string): void; - handleAttributeValueChange(value: string): void; -} - -const attributeValueExamples: AttributeExamples = { - username: 'elastic,*_system', - email: 'user@example.com,*@example.org', - metadata: '{"_reserved": true}', -}; - -export const AttributeSelector: React.FC = ({ - attributeName, - attributeValue = '', - attributeValueInvalid, - attributes, - elasticsearchRoles, - disabled, - handleAttributeSelectorChange, - handleAttributeValueChange, -}) => { - return ( -
    - - ({ value: attribute, text: attribute }))} - onChange={(e) => { - handleAttributeSelectorChange(e.target.value, elasticsearchRoles[0]); - }} - fullWidth - disabled={disabled} - /> - - - {attributeName === 'role' ? ( - ({ - value: elasticsearchRole, - text: elasticsearchRole, - }))} - onChange={(e) => { - handleAttributeValueChange(e.target.value); - }} - fullWidth - disabled={disabled} - /> - ) : ( - { - handleAttributeValueChange(e.target.value); - }} - fullWidth - disabled={disabled} - /> - )} - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts deleted file mode 100644 index 0e9f6ee992d76..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts +++ /dev/null @@ -1,464 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -import { ProductName } from '../types'; - -export const ADD_ROLE_MAPPING_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel', - { - defaultMessage: 'Add mapping', - } -); - -export const AUTH_ANY_PROVIDER_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.anyAuthProviderLabel', - { - defaultMessage: 'Any current or future Auth Provider', - } -); - -export const AUTH_INDIVIDUAL_PROVIDER_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.individualAuthProviderLabel', - { - defaultMessage: 'Select individual auth providers', - } -); - -export const ATTRIBUTE_SELECTOR_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.attributeSelectorTitle', - { - defaultMessage: 'Attribute mapping', - } -); - -export const ROLE_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.roleLabel', { - defaultMessage: 'Role', -}); - -export const USERNAME_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.usernameLabel', { - defaultMessage: 'Username', -}); - -export const EMAIL_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.emailLabel', { - defaultMessage: 'Email', -}); - -export const ALL_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.allLabel', { - defaultMessage: 'All', -}); - -export const GROUPS_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.groupsLabel', { - defaultMessage: 'Groups', -}); - -export const ENGINES_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.enginesLabel', { - defaultMessage: 'Engines', -}); - -export const EXTERNAL_ATTRIBUTE_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.externalAttributeLabel', - { - defaultMessage: 'External attribute', - } -); - -export const ATTRIBUTE_VALUE_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.attributeValueLabel', - { - defaultMessage: 'Attribute value', - } -); - -export const REMOVE_ROLE_MAPPING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.removeRoleMappingTitle', - { - defaultMessage: 'Remove role mapping', - } -); - -export const DELETE_ROLE_MAPPING_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.deleteRoleMappingDescription', - { - defaultMessage: 'Please note that deleting a mapping is permanent and cannot be undone', - } -); - -export const REMOVE_ROLE_MAPPING_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.removeRoleMappingButton', - { - defaultMessage: 'Remove mapping', - } -); - -export const REMOVE_USER_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.removeUserButton', - { - defaultMessage: 'Remove user', - } -); - -export const FILTER_ROLE_MAPPINGS_PLACEHOLDER = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.filterRoleMappingsPlaceholder', - { - defaultMessage: 'Filter role mappings', - } -); - -export const ROLE_MAPPINGS_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.roleMappingsTitle', - { - defaultMessage: 'Users and roles', - } -); - -export const ADD_ROLE_MAPPING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.newRoleMappingTitle', - { defaultMessage: 'Add role mapping' } -); - -export const MANAGE_ROLE_MAPPING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.manageRoleMappingTitle', - { defaultMessage: 'Manage role mapping' } -); - -export const ROLE_MAPPING_NOT_FOUND = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.notFoundMessage', - { - defaultMessage: 'No matching Role mapping found.', - } -); - -export const ROLE_MAPPING_FLYOUT_CREATE_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.flyoutCreateTitle', - { - defaultMessage: 'Create a role mapping', - } -); - -export const ROLE_MAPPING_FLYOUT_UPDATE_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.flyoutUpdateTitle', - { - defaultMessage: 'Update role mapping', - } -); - -export const ROLE_MAPPING_FLYOUT_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.flyoutDescription', - { - defaultMessage: 'Assign roles and permissions based on user attributes', - } -); - -export const ROLE_MAPPING_FLYOUT_CREATE_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.roleMappingFlyoutCreateButton', - { - defaultMessage: 'Create mapping', - } -); - -export const ROLE_MAPPING_FLYOUT_UPDATE_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.roleMappingFlyoutUpdateButton', - { - defaultMessage: 'Update mapping', - } -); - -export const SAVE_ROLE_MAPPING = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.saveRoleMappingButtonLabel', - { defaultMessage: 'Save role mapping' } -); - -export const UPDATE_ROLE_MAPPING = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.updateRoleMappingButtonLabel', - { defaultMessage: 'Update role mapping' } -); - -export const ROLE_MAPPINGS_HEADING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.roleMappingsHeadingTitle', - { defaultMessage: 'Role mappings' } -); - -export const ROLE_MAPPINGS_HEADING_DESCRIPTION = (productName: ProductName) => - i18n.translate('xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDescription', { - defaultMessage: - 'Role mappings provide an interface to associate native or SAML-governed role attributes with {productName} permissions.', - values: { productName }, - }); - -export const ROLE_MAPPINGS_HEADING_DOCS_LINK = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.roleMappingsHeadingDocsLink', - { defaultMessage: 'Learn more about role mappings.' } -); - -export const ROLE_MAPPINGS_HEADING_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.roleMappingsHeadingButton', - { defaultMessage: 'Create a new role mapping' } -); - -export const ROLE_MAPPINGS_NO_RESULTS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.noResults.message', - { defaultMessage: 'No matching role mappings found' } -); - -export const ROLES_DISABLED_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.rolesDisabledTitle', - { defaultMessage: 'Role-based access is disabled' } -); - -export const ROLES_DISABLED_DESCRIPTION = (productName: ProductName) => - i18n.translate('xpack.enterpriseSearch.roleMapping.rolesDisabledDescription', { - defaultMessage: - 'All users set for this deployment currently have full access to {productName}. To restrict access and manage permissions, you must enable role-based access for Enterprise Search.', - values: { productName }, - }); - -export const ROLES_DISABLED_NOTE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.rolesDisabledNote', - { - defaultMessage: - 'Note: enabling role-based access restricts access for both App Search and Workplace Search. Once enabled, review access management for both products, if applicable.', - } -); - -export const RBAC_BUTTON_DISABLED_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.rbacButtonDisabledLabel', - { - defaultMessage: 'Enabling RBAC can be performed by a superuser.', - } -); - -export const ENABLE_ROLES_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.enableRolesButton', - { defaultMessage: 'Enable role-based access' } -); - -export const ENABLE_ROLES_LINK = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.enableRolesLink', - { defaultMessage: 'Learn more about role-based access' } -); - -export const INVITATION_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.invitationDescription', - { - defaultMessage: - 'This URL can be shared with the user, allowing them to accept the Enterprise Search invitation and set a new password', - } -); - -export const NEW_INVITATION_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.newInvitationLabel', - { defaultMessage: 'Invitation URL' } -); - -export const EXISTING_INVITATION_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.existingInvitationLabel', - { defaultMessage: 'The user has not yet accepted the invitation.' } -); - -export const INVITATION_LINK = i18n.translate('xpack.enterpriseSearch.roleMapping.invitationLink', { - defaultMessage: 'Enterprise Search Invitation Link', -}); - -export const INVITATION_LINK_COPY_ARIA_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.copyInvitationLink', - { - defaultMessage: 'Copy {invitationLink}', - values: { - invitationLink: INVITATION_LINK, - }, - } -); - -export const NO_USERS_TITLE = i18n.translate('xpack.enterpriseSearch.roleMapping.noUsersTitle', { - defaultMessage: 'No user added', -}); - -export const NO_USERS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.noUsersDescription', - { - defaultMessage: - 'Users can be added individually, for flexibility. Role mappings provide a broader interface for adding large number of users using user attributes.', - } -); - -export const ENABLE_USERS_LINK = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.enableUsersLink', - { defaultMessage: 'Learn more about user management' } -); - -export const NEW_USER_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.newUserLabel', { - defaultMessage: 'Create new user', -}); - -export const EXISTING_USER_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.existingUserLabel', - { defaultMessage: 'Add existing user' } -); - -export const USERNAME_NO_USERS_TEXT = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.usernameNoUsersText', - { defaultMessage: 'No existing user eligible for addition.' } -); - -export const REQUIRED_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.requiredLabel', { - defaultMessage: 'Required', -}); - -export const USERS_HEADING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.usersHeadingTitle', - { defaultMessage: 'Users' } -); - -export const USERS_HEADING_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.usersHeadingDescription', - { - defaultMessage: - 'User management provides granular access for individual or special permission needs. Some users may be excluded from this list. These include users from federated sources such as SAML, which are managed by role mappings, and built-in user accounts such as the “elastic” or “enterprise_search” users.', - } -); - -export const USERS_HEADING_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.usersHeadingLabel', - { defaultMessage: 'Add a new user' } -); - -export const UPDATE_USER_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.updateUserLabel', - { - defaultMessage: 'Update user', - } -); - -export const ADD_USER_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.addUserLabel', { - defaultMessage: 'Add user', -}); - -export const USER_ADDED_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.userAddedLabel', - { - defaultMessage: 'User added', - } -); - -export const USER_UPDATED_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.userUpdatedLabel', - { - defaultMessage: 'User updated', - } -); - -export const NEW_USER_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.newUserDescription', - { - defaultMessage: 'Provide granular access and permissions', - } -); - -export const UPDATE_USER_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.updateUserDescription', - { - defaultMessage: 'Manage granular access and permissions', - } -); - -export const DEACTIVATED_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.deactivatedLabel', - { - defaultMessage: 'Deactivated', - } -); - -export const INVITATION_PENDING_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.invitationPendingLabel', - { - defaultMessage: 'Invitation pending', - } -); - -export const ROLE_MODAL_TEXT = i18n.translate('xpack.enterpriseSearch.roleMapping.roleModalText', { - defaultMessage: - 'Removing a role mapping could revoke access to the currently logged-in user. Before proceeding, verify that the currently logged-in user has the appropriate access level via a different role mapping to avoid undesired behavior. This action may not take effect immediately for SAML-governed roles. Users with an active SAML session will retain access until it expires.', -}); - -export const USER_MODAL_TITLE = (username: string) => - i18n.translate('xpack.enterpriseSearch.roleMapping.userModalTitle', { - defaultMessage: 'Remove {username}', - values: { username }, - }); - -export const USER_MODAL_TEXT = i18n.translate('xpack.enterpriseSearch.roleMapping.userModalText', { - defaultMessage: - 'Removing a user immediately revokes access to the experience, unless this user’s attributes also corresponds to a role mapping for native and SAML-governed authentication, in which case associated role mappings should also be reviewed and adjusted, as needed.', -}); - -export const FILTER_USERS_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.filterUsersLabel', - { - defaultMessage: 'Filter users', - } -); - -export const NO_USERS_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.noUsersLabel', { - defaultMessage: 'No matching users found', -}); - -export const EXTERNAL_ATTRIBUTE_TOOLTIP = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.externalAttributeTooltip', - { - defaultMessage: - 'External attributes are defined by the identity provider, and varies from service to service.', - } -); -export const DEACTIVATED_USER_CALLOUT_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutLabel', - { - defaultMessage: 'User deactivated', - } -); - -export const DEACTIVATED_USER_CALLOUT_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.deactivatedUserCalloutDescription', - { - defaultMessage: - 'This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console.', - } -); - -export const SMTP_CALLOUT_LABEL = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.smtpCalloutLabel', - { - defaultMessage: 'Personalized invitations will be automatically sent when an Enterprise Search', - } -); - -export const SMTP_LINK_LABEL = i18n.translate('xpack.enterpriseSearch.roleMapping.smtpLinkLabel', { - defaultMessage: 'SMTP configuration is provided', -}); - -export const KIBANA_ACCESS_WARNING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.kibanaAccessWarningTitle', - { - defaultMessage: 'Kibana access warning', - } -); - -export const KIBANA_ACCESS_WARNING_ERROR_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.kibanaAccessWarningErrorMessage', - { - defaultMessage: - 'This Elasticsearch user does not have an Enterprise Search role in Elasticsearch. They may not have access to Kibana.', - } -); - -export const KIBANA_ACCESS_WARNING_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.roleMapping.kibanaAccessWarningDescription', - { - defaultMessage: 'Consider giving them the "enterprise-search-user" role.', - } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.test.tsx deleted file mode 100644 index 0a17d0a5e638a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { DeactivatedUserCallout } from '.'; - -describe('DeactivatedUserCallout', () => { - it('renders with new', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchInlineSnapshot(` - - - - - - User deactivated - - - - - This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console. - - - - `); - }); - - it('renders with existing', () => { - const wrapper = shallow(); - - expect(wrapper).toMatchInlineSnapshot(` - - - - - - - User deactivated - - - - - This user is not currently active, and access has been temporarily revoked. Users can be re-activated via the User Management area of the Kibana console. - - - - `); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx deleted file mode 100644 index 84d4b8e512f99..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { EuiSpacer, EuiText, EuiIcon } from '@elastic/eui'; - -interface Props { - isNew: boolean; -} - -import { DEACTIVATED_USER_CALLOUT_LABEL, DEACTIVATED_USER_CALLOUT_DESCRIPTION } from './constants'; - -export const DeactivatedUserCallout: React.FC = ({ isNew }) => ( - <> - {!isNew && } - - {DEACTIVATED_USER_CALLOUT_LABEL} - - - {DEACTIVATED_USER_CALLOUT_DESCRIPTION} - - -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts deleted file mode 100644 index c10fc5c9d8242..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 * from './types'; -export { AttributeSelector } from './attribute_selector'; -export { DeactivatedUserCallout } from './deactivated_user_callout'; -export { RolesEmptyPrompt } from './roles_empty_prompt'; -export { RoleMappingsTable } from './role_mappings_table'; -export { RoleOptionLabel } from './role_option_label'; -export { RoleSelector } from './role_selector'; -export { RoleMappingFlyout } from './role_mapping_flyout'; -export { RoleMappingsHeading } from './role_mappings_heading'; -export { UserAddedInfo } from './user_added_info'; -export { UserFlyout } from './user_flyout'; -export { UsersHeading } from './users_heading'; -export { UserInvitationCallout } from './user_invitation_callout'; -export { UserSelector } from './user_selector'; -export { UsersTable } from './users_table'; -export { UsersAndRolesRowActions } from './users_and_roles_row_actions'; -export { UsersEmptyPrompt } from './users_empty_prompt'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx deleted file mode 100644 index 651a46f5df85e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 { EuiFlyout } from '@elastic/eui'; - -import { - ROLE_MAPPING_FLYOUT_CREATE_TITLE, - ROLE_MAPPING_FLYOUT_UPDATE_TITLE, - ROLE_MAPPING_FLYOUT_CREATE_BUTTON, - ROLE_MAPPING_FLYOUT_UPDATE_BUTTON, -} from './constants'; -import { RoleMappingFlyout } from './role_mapping_flyout'; - -describe('RoleMappingFlyout', () => { - const closeUsersAndRolesFlyout = jest.fn(); - const handleSaveMapping = jest.fn(); - - const props = { - isNew: true, - disabled: false, - formLoading: false, - closeUsersAndRolesFlyout, - handleSaveMapping, - }; - - it('renders for new mapping', () => { - const wrapper = shallow( - -
    - - ); - - expect(wrapper.find(EuiFlyout)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="FlyoutTitle"]').prop('children')).toEqual( - ROLE_MAPPING_FLYOUT_CREATE_TITLE - ); - expect(wrapper.find('[data-test-subj="FlyoutButton"]').prop('children')).toEqual( - ROLE_MAPPING_FLYOUT_CREATE_BUTTON - ); - }); - - it('renders for existing mapping', () => { - const wrapper = shallow( - -
    - - ); - - expect(wrapper.find(EuiFlyout)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="FlyoutTitle"]').prop('children')).toEqual( - ROLE_MAPPING_FLYOUT_UPDATE_TITLE - ); - expect(wrapper.find('[data-test-subj="FlyoutButton"]').prop('children')).toEqual( - ROLE_MAPPING_FLYOUT_UPDATE_BUTTON - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx deleted file mode 100644 index 5f4ba7ba4cb7a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mapping_flyout.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiPortal, - EuiText, - EuiTitle, - EuiSpacer, -} from '@elastic/eui'; - -import { CANCEL_BUTTON_LABEL } from '../constants/actions'; - -import { - ROLE_MAPPING_FLYOUT_CREATE_TITLE, - ROLE_MAPPING_FLYOUT_UPDATE_TITLE, - ROLE_MAPPING_FLYOUT_DESCRIPTION, - ROLE_MAPPING_FLYOUT_CREATE_BUTTON, - ROLE_MAPPING_FLYOUT_UPDATE_BUTTON, -} from './constants'; - -interface Props { - children: React.ReactNode; - isNew: boolean; - disabled: boolean; - formLoading: boolean; - closeUsersAndRolesFlyout(): void; - handleSaveMapping(): void; -} - -export const RoleMappingFlyout: React.FC = ({ - children, - isNew, - disabled, - formLoading, - closeUsersAndRolesFlyout, - handleSaveMapping, -}) => ( - - - - -

    - {isNew ? ROLE_MAPPING_FLYOUT_CREATE_TITLE : ROLE_MAPPING_FLYOUT_UPDATE_TITLE} -

    -
    - -

    {ROLE_MAPPING_FLYOUT_DESCRIPTION}

    -
    -
    - - {children} - - - - - - - {CANCEL_BUTTON_LABEL} - - - - - {isNew ? ROLE_MAPPING_FLYOUT_CREATE_BUTTON : ROLE_MAPPING_FLYOUT_UPDATE_BUTTON} - - - - -
    -
    -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx deleted file mode 100644 index 5a2958d60dc2c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.test.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { EuiTitle, EuiLink, EuiButton, EuiText } from '@elastic/eui'; - -import { RoleMappingsHeading } from './role_mappings_heading'; - -describe('RoleMappingsHeading', () => { - it('renders ', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(EuiTitle)).toHaveLength(1); - expect(wrapper.find(EuiText)).toHaveLength(1); - expect(wrapper.find(EuiLink)).toHaveLength(1); - expect(wrapper.find(EuiButton)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx deleted file mode 100644 index 1984cc6c60a34..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_heading.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; - -import { ProductName } from '../types'; - -import { - ROLE_MAPPINGS_HEADING_TITLE, - ROLE_MAPPINGS_HEADING_DESCRIPTION, - ROLE_MAPPINGS_HEADING_DOCS_LINK, - ROLE_MAPPINGS_HEADING_BUTTON, -} from './constants'; - -interface Props { - productName: ProductName; - docsLink: string; - onClick(): void; -} - -export const RoleMappingsHeading: React.FC = ({ productName, docsLink, onClick }) => ( -
    - - - -

    {ROLE_MAPPINGS_HEADING_TITLE}

    -
    - - -

    - {ROLE_MAPPINGS_HEADING_DESCRIPTION(productName)}{' '} - - {ROLE_MAPPINGS_HEADING_DOCS_LINK} - -

    -
    -
    - - - {ROLE_MAPPINGS_HEADING_BUTTON} - - -
    - -
    -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss deleted file mode 100644 index 0973d5d26fdbc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss +++ /dev/null @@ -1,5 +0,0 @@ -.roleMappingsTable { - td { - vertical-align: top; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx deleted file mode 100644 index 93918dca46bfd..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 { wsRoleMapping, asRoleMapping } from './__mocks__/roles'; - -import React from 'react'; - -import { mount } from 'enzyme'; -import { act } from 'react-dom/test-utils'; - -import { EuiInMemoryTable, EuiTableHeaderCell, EuiTableRow } from '@elastic/eui'; -import type { EuiSearchBarProps } from '@elastic/eui'; - -import { engines } from '../../app_search/__mocks__/engines.mock'; - -import { RoleMappingsTable } from './role_mappings_table'; -import { UsersAndRolesRowActions } from './users_and_roles_row_actions'; - -describe('RoleMappingsTable', () => { - const initializeRoleMapping = jest.fn(); - const handleDeleteMapping = jest.fn(); - const roleMappings = [ - { - ...wsRoleMapping, - accessItems: [ - { - name: 'foo', - }, - ], - }, - ]; - - const props = { - accessItemKey: 'groups' as 'groups' | 'engines', - accessHeader: 'access', - roleMappings, - addMappingButton: