diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 89ebb4aa12cd4..6d42c030b2d4f 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -81,6 +81,7 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/genai/knowledge_base/entries/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/serverless.config.ts diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml index 77a8c57029096..a2390fa2bd27f 100644 --- a/.buildkite/ftr_security_stateful_configs.yml +++ b/.buildkite/ftr_security_stateful_configs.yml @@ -62,6 +62,7 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/user_roles/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts diff --git a/.buildkite/scripts/steps/esql_generate_function_metadata.sh b/.buildkite/scripts/steps/esql_generate_function_metadata.sh index 837a962b3c42b..07de4bc9bd04c 100755 --- a/.buildkite/scripts/steps/esql_generate_function_metadata.sh +++ b/.buildkite/scripts/steps/esql_generate_function_metadata.sh @@ -2,7 +2,7 @@ set -euo pipefail VALIDATION_PACKAGE_DIR="packages/kbn-esql-validation-autocomplete" -EDITOR_PACKAGE_DIR="packages/kbn-text-based-editor" +EDITOR_PACKAGE_DIR="packages/kbn-language-documentation-popover" GIT_SCOPE="$VALIDATION_PACKAGE_DIR/**/* $EDITOR_PACKAGE_DIR/**/*" report_main_step () { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9284e5ba821f9..7ec76d03da9e0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1361,6 +1361,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib # Kibana Platform Security /.github/codeql @elastic/kibana-security /.github/workflows/codeql.yml @elastic/kibana-security +/.github/workflows/codeql-stats.yml @elastic/kibana-security /src/dev/eslint/security_eslint_rule_tests.ts @elastic/kibana-security /src/core/server/integration_tests/config/check_dynamic_config.test.ts @elastic/kibana-security /src/plugins/telemetry/server/config/telemetry_labels.ts @elastic/kibana-security diff --git a/.github/workflows/codeql-stats.yml b/.github/workflows/codeql-stats.yml new file mode 100644 index 0000000000000..fc01cdd969295 --- /dev/null +++ b/.github/workflows/codeql-stats.yml @@ -0,0 +1,28 @@ +name: CodeQL statistics + +on: + schedule: + - cron: '27 0 * * 1' # At 00:27 every Monday + +jobs: + stats: + name: CodeQL statistics + runs-on: ubuntu-latest + if: github.repository == 'elastic/kibana' # Hack: Do not run on forks + steps: + - name: Checkout kibana-operations + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: 'elastic/kibana-operations' + ref: main + path: ./kibana-operations + token: ${{secrets.KIBANAMACHINE_TOKEN}} + + - name: CodeQL alert statistics + working-directory: ./kibana-operations/triage + env: + GITHUB_TOKEN: ${{secrets.KIBANAMACHINE_TOKEN}} + SLACK_TOKEN: ${{secrets.CODE_SCANNING_SLACK_TOKEN}} + run: | + npm ci --omit=dev + node codeql-alert-stats diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 87a022376ac93..82080dd9235f9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -76,12 +76,3 @@ jobs: run: | npm ci --omit=dev node codeql-alert - - - name: CodeQL alert statistics - working-directory: ./kibana-operations/triage - env: - GITHUB_TOKEN: ${{secrets.KIBANAMACHINE_TOKEN}} - SLACK_TOKEN: ${{secrets.CODE_SCANNING_SLACK_TOKEN}} - run: | - npm ci --omit=dev - node codeql-alert-stats diff --git a/packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx b/packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx index 444ff70ff9128..d400f67993f8a 100644 --- a/packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx +++ b/packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx @@ -21,6 +21,7 @@ const createOpts = async (props: KibanaConnectionDetailsProviderProps) => { const { http, docLinks, analytics } = start.core; const locator = start.plugins?.share?.url?.locators.get('MANAGEMENT_APP_LOCATOR'); const manageKeysLink = await locator?.getUrl({ sectionId: 'security', appId: 'api_keys' }); + const elasticsearchConfig = await start.plugins?.cloud?.fetchElasticsearchConfig(); const result: ConnectionDetailsOpts = { ...options, navigateToUrl: start.core.application @@ -35,7 +36,7 @@ const createOpts = async (props: KibanaConnectionDetailsProviderProps) => { }, endpoints: { id: start.plugins?.cloud?.cloudId, - url: start.plugins?.cloud?.elasticsearchUrl, + url: elasticsearchConfig?.elasticsearchUrl, cloudIdLearMoreLink: docLinks?.links?.cloud?.beatsAndLogstashConfiguration, ...options?.endpoints, }, diff --git a/packages/cloud/deployment_details/services.tsx b/packages/cloud/deployment_details/services.tsx index d3ca0a340b600..73959627e98e6 100644 --- a/packages/cloud/deployment_details/services.tsx +++ b/packages/cloud/deployment_details/services.tsx @@ -6,8 +6,7 @@ * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ - -import React, { FC, PropsWithChildren, useContext } from 'react'; +import React, { FC, PropsWithChildren, useContext, useEffect } from 'react'; export interface DeploymentDetailsContextValue { cloudId?: string; @@ -58,7 +57,7 @@ export interface DeploymentDetailsKibanaDependencies { cloud: { isCloudEnabled: boolean; cloudId?: string; - elasticsearchUrl?: string; + fetchElasticsearchConfig: () => Promise<{ elasticsearchUrl?: string }>; }; /** DocLinksStart contract */ docLinks: { @@ -79,11 +78,19 @@ export interface DeploymentDetailsKibanaDependencies { export const DeploymentDetailsKibanaProvider: FC< PropsWithChildren > = ({ children, ...services }) => { + const [elasticsearchUrl, setElasticsearchUrl] = React.useState(''); + + useEffect(() => { + services.cloud.fetchElasticsearchConfig().then((config) => { + setElasticsearchUrl(config.elasticsearchUrl || ''); + }); + }, [services.cloud]); + const { core: { application: { navigateToUrl }, }, - cloud: { isCloudEnabled, cloudId, elasticsearchUrl }, + cloud: { isCloudEnabled, cloudId }, share: { url: { locators }, }, diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts index 34a8bc07e0b5c..b7d7b40c49806 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts @@ -47,6 +47,7 @@ test('set correct defaults', () => { "maxSockets": 800, "password": undefined, "pingTimeout": "PT30S", + "publicBaseUrl": undefined, "requestHeadersWhitelist": Array [ "authorization", "es-client-authentication", diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts index 46b7a02768e7a..93fb64baf46d0 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts @@ -359,6 +359,13 @@ export class ElasticsearchConfig implements IElasticsearchConfig { */ public readonly hosts: string[]; + /** + * Optional host that users can use to connect to your Elasticsearch cluster, + * this URL will be shown in Kibana as the Elasticsearch URL + */ + + public readonly publicBaseUrl?: string; + /** * List of Kibana client-side headers to send to Elasticsearch when request * scoped cluster client is used. If this is an empty array then *no* client-side @@ -473,6 +480,7 @@ export class ElasticsearchConfig implements IElasticsearchConfig { this.skipStartupConnectionCheck = rawConfig.skipStartupConnectionCheck; this.apisToRedactInLogs = rawConfig.apisToRedactInLogs; this.dnsCacheTtl = rawConfig.dnsCacheTtl; + this.publicBaseUrl = rawConfig.publicBaseUrl; const { alwaysPresentCertificate, verificationMode } = rawConfig.ssl; const { key, keyPassphrase, certificate, certificateAuthorities } = readKeyAndCerts(rawConfig); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts index 5a3f34d0565f8..83eb04832121e 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts @@ -135,6 +135,7 @@ export class ElasticsearchService agentStatsProvider: { getAgentsStats: agentManager.getAgentsStats.bind(agentManager), }, + publicBaseUrl: config.publicBaseUrl, }; } @@ -194,6 +195,7 @@ export class ElasticsearchService metrics: { elasticsearchWaitTime, }, + publicBaseUrl: config.publicBaseUrl, }; } diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/contracts.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/contracts.ts index f0a3a62d08f18..bc712a61a535e 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/contracts.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/contracts.ts @@ -138,6 +138,12 @@ export interface ElasticsearchServiceStart { * Returns the capabilities for the default cluster. */ getCapabilities: () => ElasticsearchCapabilities; + + /** + * The public base URL (if any) that should be used by end users to access the Elasticsearch cluster. + */ + + readonly publicBaseUrl?: string; } /** diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index 539b629974982..2fcdf384cb897 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -212,6 +212,7 @@ export function createPluginSetupContext({ docLinks: deps.docLinks, elasticsearch: { legacy: deps.elasticsearch.legacy, + publicBaseUrl: deps.elasticsearch.publicBaseUrl, setUnauthorizedErrorHandler: deps.elasticsearch.setUnauthorizedErrorHandler, }, executionContext: { diff --git a/packages/kbn-esql-utils/src/utils/append_to_query.ts b/packages/kbn-esql-utils/src/utils/append_to_query.ts index 76f317d55aa5d..f4161be073a8d 100644 --- a/packages/kbn-esql-utils/src/utils/append_to_query.ts +++ b/packages/kbn-esql-utils/src/utils/append_to_query.ts @@ -36,7 +36,7 @@ export function appendWhereClauseToESQLQuery( default: operator = '=='; } - let filterValue = typeof value === 'string' ? `"${value.replace(/"/g, '\\"')}"` : value; + let filterValue = typeof value === 'string' ? `"${value.replace(/\"/g, '\\"')}"` : value; // Adding the backticks here are they are needed for special char fields let fieldName = `\`${field}\``; diff --git a/packages/kbn-language-documentation-popover/index.ts b/packages/kbn-language-documentation-popover/index.ts index a417382773a54..c12962fdf22b2 100644 --- a/packages/kbn-language-documentation-popover/index.ts +++ b/packages/kbn-language-documentation-popover/index.ts @@ -6,7 +6,8 @@ * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ - -export { LanguageDocumentationPopover } from './src/components/documentation_popover'; -export { LanguageDocumentationPopoverContent } from './src/components/documentation_content'; -export type { LanguageDocumentationSections } from './src/components/documentation_content'; +export { LanguageDocumentationPopover } from './src/components/as_popover'; +export { LanguageDocumentationPopoverContent } from './src/components/as_popover/popover_content'; +export { LanguageDocumentationFlyout } from './src/components/as_flyout'; +export { LanguageDocumentationInline } from './src/components/as_inline'; +export type { LanguageDocumentationSections } from './src/types'; diff --git a/packages/kbn-language-documentation-popover/package.json b/packages/kbn-language-documentation-popover/package.json index a756b25061b64..002c3c4ee51b3 100644 --- a/packages/kbn-language-documentation-popover/package.json +++ b/packages/kbn-language-documentation-popover/package.json @@ -5,5 +5,10 @@ "private": true, "sideEffects": [ "*.scss" - ] -} \ No newline at end of file + ], + "scripts": { + "make:docs": "ts-node --transpileOnly scripts/generate_esql_docs.ts", + "postmake:docs": "yarn run lint:fix", + "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-language-documentation-popover/src/sections/generated" + } +} diff --git a/packages/kbn-text-based-editor/scripts/generate_esql_docs.ts b/packages/kbn-language-documentation-popover/scripts/generate_esql_docs.ts similarity index 92% rename from packages/kbn-text-based-editor/scripts/generate_esql_docs.ts rename to packages/kbn-language-documentation-popover/scripts/generate_esql_docs.ts index 8a38908e2b211..4fad23e2e25f2 100644 --- a/packages/kbn-text-based-editor/scripts/generate_esql_docs.ts +++ b/packages/kbn-language-documentation-popover/scripts/generate_esql_docs.ts @@ -11,18 +11,18 @@ import * as recast from 'recast'; const n = recast.types.namedTypes; import fs from 'fs'; import path from 'path'; -import { functions } from '../src/inline_documentation/generated/scalar_functions'; +import { functions } from '../src/sections/generated/scalar_functions'; (function () { const pathToElasticsearch = process.argv[2]; const { scalarFunctions, aggregationFunctions } = loadFunctionDocs(pathToElasticsearch); writeFunctionDocs( scalarFunctions, - path.join(__dirname, '../src/inline_documentation/generated/scalar_functions.tsx') + path.join(__dirname, '../src/sections/generated/scalar_functions.tsx') ); writeFunctionDocs( aggregationFunctions, - path.join(__dirname, '../src/inline_documentation/generated/aggregation_functions.tsx') + path.join(__dirname, '../src/sections/generated/aggregation_functions.tsx') ); })(); @@ -86,7 +86,7 @@ function writeFunctionDocs(functionDocs: Map, pathToDocsFile: st // Do not edit manually... automatically generated by scripts/generate_esql_docs.ts { label: i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.${name}', + 'languageDocumentationPopover.documentationESQL.${name}', { defaultMessage: '${name.toUpperCase()}', } @@ -97,7 +97,7 @@ function writeFunctionDocs(functionDocs: Map, pathToDocsFile: st readOnly enableSoftLineBreaks markdownContent={i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.${name}.markdown', + 'languageDocumentationPopover.documentationESQL.${name}.markdown', { defaultMessage: \`${docWithoutLinks.replaceAll('`', '\\`')}\`, description: diff --git a/src/plugins/controls/public/services/index.ts b/packages/kbn-language-documentation-popover/setup_tests.ts similarity index 82% rename from src/plugins/controls/public/services/index.ts rename to packages/kbn-language-documentation-popover/setup_tests.ts index a7cc0715e08d9..5ebc6d3dac1ca 100644 --- a/src/plugins/controls/public/services/index.ts +++ b/packages/kbn-language-documentation-popover/setup_tests.ts @@ -7,4 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { pluginServices } from './plugin_services'; +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx b/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx index 658f1fe81129c..06ace5c916201 100644 --- a/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx +++ b/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; -import { LanguageDocumentationPopover } from '../components/documentation_popover'; +import { LanguageDocumentationPopover } from '../components/as_popover'; const sections = { groups: [ diff --git a/packages/kbn-language-documentation-popover/src/components/as_flyout/index.test.tsx b/packages/kbn-language-documentation-popover/src/components/as_flyout/index.test.tsx new file mode 100644 index 0000000000000..5dd66386c4188 --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/components/as_flyout/index.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { screen, render, fireEvent, waitFor } from '@testing-library/react'; +import { LanguageDocumentationFlyout } from '.'; + +jest.mock('../../sections', () => { + const module = jest.requireActual('../../sections'); + return { + ...module, + getESQLDocsSections: () => ({ + groups: [ + { + label: 'Section one', + description: 'Section 1 description', + items: [], + }, + { + label: 'Section two', + items: [ + { + label: 'Section two item 1', + description: 'Section two item 1 description', + }, + { + label: 'Section two item 2', + description: 'Section two item 2 description', + }, + ], + }, + { + label: 'Section three', + items: [ + { + label: 'Section three item 1', + description: 'Section three item 1 description', + }, + { + label: 'Section three item 2', + description: 'Section three item 2 description', + }, + ], + }, + ], + initialSection: Here is the initial section, + }), + }; +}); + +describe('###Documentation flyout component', () => { + const renderFlyout = (linkToDocumentation?: string) => { + return render( + + ); + }; + it('has a header element for navigation through the sections', () => { + renderFlyout(); + expect(screen.getByTestId('language-documentation-navigation-search')).toBeInTheDocument(); + expect(screen.getByTestId('language-documentation-navigation-dropdown')).toBeInTheDocument(); + expect(screen.queryByTestId('language-documentation-navigation-link')).not.toBeInTheDocument(); + }); + + it('has a link if linkToDocumentation prop is given', () => { + renderFlyout('meow'); + expect(screen.getByTestId('language-documentation-navigation-link')).toBeInTheDocument(); + }); + + it('contains the two last sections', async () => { + renderFlyout(); + await waitFor(() => { + expect(screen.getByText('Section two')).toBeInTheDocument(); + expect(screen.getByText('Section three')).toBeInTheDocument(); + }); + }); + + it('contains the correct section if user updates the search input', async () => { + renderFlyout(); + const input = screen.getByTestId('language-documentation-navigation-search'); + fireEvent.change(input, { target: { value: 'two' } }); + await waitFor(() => { + expect(screen.getByText('Section two')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/kbn-language-documentation-popover/src/components/as_flyout/index.tsx b/packages/kbn-language-documentation-popover/src/components/as_flyout/index.tsx new file mode 100644 index 0000000000000..0a617165f7661 --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/components/as_flyout/index.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react'; +import { + EuiFlyout, + useEuiTheme, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { getFilteredGroups } from '../../utils/get_filtered_groups'; +import { DocumentationMainContent, DocumentationNavigation } from '../shared'; +import { getESQLDocsSections } from '../../sections'; +import type { LanguageDocumentationSections } from '../../types'; + +interface DocumentationFlyoutProps { + isHelpMenuOpen: boolean; + onHelpMenuVisibilityChange: (status: boolean) => void; + searchInDescription?: boolean; + linkToDocumentation?: string; +} + +function DocumentationFlyout({ + searchInDescription, + linkToDocumentation, + isHelpMenuOpen, + onHelpMenuVisibilityChange, +}: DocumentationFlyoutProps) { + const [documentationSections, setDocumentationSections] = + useState(); + + const { euiTheme } = useEuiTheme(); + const DEFAULT_WIDTH = euiTheme.base * 34; + + const [selectedSection, setSelectedSection] = useState(); + const [searchText, setSearchText] = useState(''); + + const scrollTargets = useRef>({}); + + const onNavigationChange = useCallback((selectedOptions) => { + setSelectedSection(selectedOptions.length ? selectedOptions[0].label : undefined); + if (selectedOptions.length) { + const scrollToElement = scrollTargets.current[selectedOptions[0].label]; + scrollToElement.scrollIntoView(); + } + }, []); + + useEffect(() => { + onHelpMenuVisibilityChange(isHelpMenuOpen ?? false); + }, [isHelpMenuOpen, onHelpMenuVisibilityChange]); + + useEffect(() => { + async function getDocumentation() { + const sections = await getESQLDocsSections(); + setDocumentationSections(sections); + } + if (!documentationSections) { + getDocumentation(); + } + }, [documentationSections]); + + const filteredGroups = useMemo(() => { + return getFilteredGroups(searchText, searchInDescription, documentationSections, 1); + }, [documentationSections, searchText, searchInDescription]); + + return ( + <> + {isHelpMenuOpen && ( + onHelpMenuVisibilityChange(false)} + aria-labelledby="esqlInlineDocumentationFlyout" + type="push" + size={DEFAULT_WIDTH} + paddingSize="m" + > + + +

+ {i18n.translate('languageDocumentationPopover.documentationFlyoutTitle', { + defaultMessage: 'ES|QL quick reference', + })} +

+
+ + +
+ + + +
+ )} + + ); +} + +export const LanguageDocumentationFlyout = React.memo(DocumentationFlyout); diff --git a/packages/kbn-language-documentation-popover/src/components/as_inline/index.test.tsx b/packages/kbn-language-documentation-popover/src/components/as_inline/index.test.tsx new file mode 100644 index 0000000000000..4ba873614f9b2 --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/components/as_inline/index.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { screen, render, fireEvent, waitFor } from '@testing-library/react'; +import { Markdown } from '@kbn/shared-ux-markdown'; +import { LanguageDocumentationInline } from '.'; + +const mockMarkDownDescription = () => ( + +); + +jest.mock('../../sections', () => { + const module = jest.requireActual('../../sections'); + return { + ...module, + getESQLDocsSections: () => ({ + groups: [ + { + label: 'Section one', + description: 'Section 1 description', + items: [], + }, + { + label: 'Section two', + items: [ + { + label: 'Section two item 1', + description: 'Section two item 1 description', + }, + { + label: 'Section two item 2', + description: 'Section two item 2 description', + }, + ], + }, + { + label: 'Section three', + items: [ + { + label: 'Section three item 1', + description: mockMarkDownDescription(), + }, + { + label: 'Section three item 2', + description: 'Section three item 2 description', + }, + ], + }, + ], + initialSection: Here is the initial section, + }), + }; +}); + +describe('###Documentation flyout component', () => { + const renderInlineComponent = (searchInDescription = false) => { + return render(); + }; + it('has a header element for navigation through the sections', () => { + renderInlineComponent(); + expect(screen.getByTestId('language-documentation-navigation-search')).toBeInTheDocument(); + expect(screen.getByTestId('language-documentation-navigation-dropdown')).toBeInTheDocument(); + }); + + it('contains the two last sections', async () => { + renderInlineComponent(); + await waitFor(() => { + expect(screen.getByText('Section two')).toBeInTheDocument(); + expect(screen.getByText('Section three')).toBeInTheDocument(); + }); + }); + + it('contains the correct section if user updates the search input', async () => { + renderInlineComponent(); + const input = screen.getByTestId('language-documentation-navigation-search'); + fireEvent.change(input, { target: { value: 'two' } }); + await waitFor(() => { + expect(screen.getByText('Section two')).toBeInTheDocument(); + }); + }); + + it('contains the correct section if user updates the search input with a text that exist in the description', async () => { + renderInlineComponent(true); + const input = screen.getByTestId('language-documentation-navigation-search'); + fireEvent.change(input, { target: { value: 'blah' } }); + await waitFor(() => { + expect(screen.getByText('Section three')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/kbn-language-documentation-popover/src/components/as_inline/index.tsx b/packages/kbn-language-documentation-popover/src/components/as_inline/index.tsx new file mode 100644 index 0000000000000..dcc860aa70db2 --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/components/as_inline/index.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import React, { useCallback, useState, useRef, useMemo, useEffect } from 'react'; +import { css } from '@emotion/react'; +import { useEuiTheme, euiScrollBarStyles, EuiSpacer } from '@elastic/eui'; +import { getFilteredGroups } from '../../utils/get_filtered_groups'; +import { DocumentationMainContent, DocumentationNavigation } from '../shared'; +import type { LanguageDocumentationSections } from '../../types'; +import { getESQLDocsSections } from '../../sections'; + +interface DocumentationInlineProps { + searchInDescription?: boolean; +} + +const MAX_HEIGHT = 250; + +function DocumentationInline({ searchInDescription }: DocumentationInlineProps) { + const theme = useEuiTheme(); + const [documentationSections, setDocumentationSections] = + useState(); + const scrollBarStyles = euiScrollBarStyles(theme); + const [selectedSection, setSelectedSection] = useState(); + const [searchText, setSearchText] = useState(''); + + const scrollTargets = useRef>({}); + + useEffect(() => { + async function getDocumentation() { + const sections = await getESQLDocsSections(); + setDocumentationSections(sections); + } + if (!documentationSections) { + getDocumentation(); + } + }, [documentationSections]); + + const filteredGroups = useMemo(() => { + return getFilteredGroups(searchText, searchInDescription, documentationSections, 1); + }, [documentationSections, searchText, searchInDescription]); + + const onNavigationChange = useCallback((selectedOptions) => { + setSelectedSection(selectedOptions.length ? selectedOptions[0].label : undefined); + if (selectedOptions.length) { + const scrollToElement = scrollTargets.current[selectedOptions[0].label]; + scrollToElement.scrollIntoView(); + } + }, []); + + return ( +
+ + + +
+ ); +} + +export const LanguageDocumentationInline = React.memo(DocumentationInline); diff --git a/packages/kbn-language-documentation-popover/src/components/documentation.scss b/packages/kbn-language-documentation-popover/src/components/as_popover/documentation.scss similarity index 100% rename from packages/kbn-language-documentation-popover/src/components/documentation.scss rename to packages/kbn-language-documentation-popover/src/components/as_popover/documentation.scss diff --git a/packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx b/packages/kbn-language-documentation-popover/src/components/as_popover/index.tsx similarity index 95% rename from packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx rename to packages/kbn-language-documentation-popover/src/components/as_popover/index.tsx index 265e3304f7d90..9a1432e938f04 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx +++ b/packages/kbn-language-documentation-popover/src/components/as_popover/index.tsx @@ -16,10 +16,8 @@ import { EuiButtonIconProps, EuiOutsideClickDetector, } from '@elastic/eui'; -import { - type LanguageDocumentationSections, - LanguageDocumentationPopoverContent, -} from './documentation_content'; +import { LanguageDocumentationPopoverContent } from './popover_content'; +import type { LanguageDocumentationSections } from '../../types'; interface DocumentationPopoverProps { language: string; diff --git a/packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx b/packages/kbn-language-documentation-popover/src/components/as_popover/popover_content.test.tsx similarity index 97% rename from packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx rename to packages/kbn-language-documentation-popover/src/components/as_popover/popover_content.test.tsx index d5acdad75dae4..a7a4bce8c2cd5 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx +++ b/packages/kbn-language-documentation-popover/src/components/as_popover/popover_content.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { mountWithIntl, findTestSubject } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { Markdown } from '@kbn/shared-ux-markdown'; -import { LanguageDocumentationPopoverContent } from './documentation_content'; +import { LanguageDocumentationPopoverContent } from './popover_content'; describe('###Documentation popover content', () => { const sections = { diff --git a/packages/kbn-language-documentation-popover/src/components/documentation_content.tsx b/packages/kbn-language-documentation-popover/src/components/as_popover/popover_content.tsx similarity index 84% rename from packages/kbn-language-documentation-popover/src/components/documentation_content.tsx rename to packages/kbn-language-documentation-popover/src/components/as_popover/popover_content.tsx index d3fedfee9f1eb..ec622eccf6044 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation_content.tsx +++ b/packages/kbn-language-documentation-popover/src/components/as_popover/popover_content.tsx @@ -6,8 +6,7 @@ * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ - -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, @@ -22,19 +21,11 @@ import { EuiSpacer, EuiLink, } from '@elastic/eui'; -import { elementToString } from '../utils/element_to_string'; +import { getFilteredGroups } from '../../utils/get_filtered_groups'; +import type { LanguageDocumentationSections } from '../../types'; import './documentation.scss'; -export interface LanguageDocumentationSections { - groups: Array<{ - label: string; - description?: string; - items: Array<{ label: string; description?: JSX.Element }>; - }>; - initialSection: JSX.Element; -} - interface DocumentationProps { language: string; sections?: LanguageDocumentationSections; @@ -61,29 +52,9 @@ function DocumentationContent({ const [searchText, setSearchText] = useState(''); - const normalizedSearchText = searchText.trim().toLocaleLowerCase(); - - const filteredGroups = sections?.groups - .map((group) => { - const items = group.items.filter((helpItem) => { - return ( - !normalizedSearchText || - helpItem.label.toLocaleLowerCase().includes(normalizedSearchText) || - // Converting the JSX element to a string first - (searchInDescription && - elementToString(helpItem.description) - ?.toLocaleLowerCase() - .includes(normalizedSearchText)) - ); - }); - return { ...group, items }; - }) - .filter((group) => { - if (group.items.length > 0 || !normalizedSearchText) { - return true; - } - return group.label.toLocaleLowerCase().includes(normalizedSearchText); - }); + const filteredGroups = useMemo(() => { + return getFilteredGroups(searchText, searchInDescription, sections); + }, [sections, searchText, searchInDescription]); return ( <> @@ -158,12 +129,12 @@ function DocumentationContent({ - {helpGroup.items.length ? ( + {helpGroup.options.length ? ( <> - {helpGroup.items.map((helpItem) => { + {helpGroup.options.map((helpItem) => { return ( ; + filteredGroups?: Array<{ + label: string; + description?: string; + options: Array<{ label: string; description?: JSX.Element | undefined }>; + }>; + sections?: LanguageDocumentationSections; +} + +function DocumentationContent({ + searchText, + scrollTargets, + filteredGroups, + sections, +}: DocumentationContentProps) { + return ( + <> + + + {!searchText && ( +
{ + if (el && sections?.groups?.length) { + scrollTargets.current[sections.groups[0].label] = el; + } + }} + > + {sections?.initialSection} +
+ )} + {filteredGroups?.map((helpGroup, index) => { + return ( +
{ + if (el) { + scrollTargets.current[helpGroup.label] = el; + } + }} + > +

{helpGroup.label}

+ +

{helpGroup.description}

+ + {filteredGroups?.[index].options.map((helpItem) => { + return ( +
{ + if (el) { + scrollTargets.current[helpItem.label] = el; + } + }} + > + {helpItem.description} +
+ ); + })} +
+ ); + })} +
+
+ + ); +} + +export const DocumentationMainContent = React.memo(DocumentationContent); diff --git a/packages/kbn-language-documentation-popover/src/components/shared/documentation_navigation.tsx b/packages/kbn-language-documentation-popover/src/components/shared/documentation_navigation.tsx new file mode 100644 index 0000000000000..c8202d0ea448f --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/components/shared/documentation_navigation.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import React from 'react'; +import { css } from '@emotion/react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiFormRow, + EuiLink, + EuiText, + useEuiTheme, + EuiFieldSearch, + EuiComboBox, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface DocumentationNavProps { + searchText: string; + setSearchText: (text: string) => void; + onNavigationChange: (selectedOptions: Array<{ label: string }>) => void; + filteredGroups?: Array<{ label: string }>; + linkToDocumentation?: string; + selectedSection?: string; +} + +function DocumentationNav({ + searchText, + setSearchText, + onNavigationChange, + filteredGroups, + linkToDocumentation, + selectedSection, +}: DocumentationNavProps) { + const { euiTheme } = useEuiTheme(); + + return ( + <> + + + + + {i18n.translate('languageDocumentationPopover.esqlDocsLinkLabel', { + defaultMessage: 'View full ES|QL documentation', + })} + + + ) + } + > + + + + + { + setSearchText(e.target.value); + }} + data-test-subj="language-documentation-navigation-search" + placeholder={i18n.translate('languageDocumentationPopover.searchPlaceholder', { + defaultMessage: 'Search', + })} + fullWidth + compressed + /> + + + + ); +} + +export const DocumentationNavigation = React.memo(DocumentationNav); diff --git a/src/plugins/controls/public/services/storage/types.ts b/packages/kbn-language-documentation-popover/src/components/shared/index.ts similarity index 75% rename from src/plugins/controls/public/services/storage/types.ts rename to packages/kbn-language-documentation-popover/src/components/shared/index.ts index 2d1e8b08e8364..f00df2358d39a 100644 --- a/src/plugins/controls/public/services/storage/types.ts +++ b/packages/kbn-language-documentation-popover/src/components/shared/index.ts @@ -7,7 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export interface ControlsStorageService { - getShowInvalidSelectionWarning: () => boolean; - setShowInvalidSelectionWarning: (value: boolean) => void; -} +export { DocumentationNavigation } from './documentation_navigation'; +export { DocumentationMainContent } from './documentation_content'; diff --git a/packages/kbn-text-based-editor/src/inline_documentation/esql_documentation_sections.tsx b/packages/kbn-language-documentation-popover/src/sections/esql_documentation_sections.tsx similarity index 79% rename from packages/kbn-text-based-editor/src/inline_documentation/esql_documentation_sections.tsx rename to packages/kbn-language-documentation-popover/src/sections/esql_documentation_sections.tsx index d0c136cf6d01e..21e550f4ca5eb 100644 --- a/packages/kbn-text-based-editor/src/inline_documentation/esql_documentation_sections.tsx +++ b/packages/kbn-language-documentation-popover/src/sections/esql_documentation_sections.tsx @@ -17,11 +17,8 @@ const Markdown = (props: Parameters[0]) => ( export const initialSection = ( ); export const sourceCommands = { - label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.sourceCommands', { + label: i18n.translate('languageDocumentationPopover.documentationESQL.sourceCommands', { defaultMessage: 'Source commands', }), description: i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.commandsDescription', + 'languageDocumentationPopover.documentationESQL.commandsDescription', { defaultMessage: `A source command produces a table, typically with data from Elasticsearch. ES|QL supports the following source commands.`, } ), items: [ { - label: i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.from', - { - defaultMessage: 'FROM', - } - ), + label: i18n.translate('languageDocumentationPopover.documentationESQL.from', { + defaultMessage: 'FROM', + }), description: ( \` source command returns information about the deployment and its capabilities: @@ -186,28 +173,25 @@ The \`SHOW \` source command returns information about the deployment and }; export const processingCommands = { - label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.processingCommands', { + label: i18n.translate('languageDocumentationPopover.documentationESQL.processingCommands', { defaultMessage: 'Processing commands', }), description: i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.processingCommandsDescription', + 'languageDocumentationPopover.documentationESQL.processingCommandsDescription', { defaultMessage: `Processing commands change an input table by adding, removing, or changing rows and columns. ES|QL supports the following processing commands.`, } ), items: [ { - label: i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.dissect', - { - defaultMessage: 'DISSECT', - } - ), + label: i18n.translate('languageDocumentationPopover.documentationESQL.dissect', { + defaultMessage: 'DISSECT', + }), description: ( \` type conversion functions. +The \`::\` operator provides a convenient alternative syntax to the \`TO_\` type conversion functions. Example: \`\`\` @@ -943,16 +882,13 @@ ROW ver = CONCAT(("0"::INT + 1)::STRING, ".2.3")::VERSION ), }, { - label: i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.inOperator', - { - defaultMessage: 'IN', - } - ), + label: i18n.translate('languageDocumentationPopover.documentationESQL.inOperator', { + defaultMessage: 'IN', + }), description: ( { + const groups: Array<{ + label: string; + description?: string; + items: Array<{ label: string; description?: JSX.Element }>; + }> = []; + const { + sourceCommands, + processingCommands, + initialSection, + scalarFunctions, + aggregationFunctions, + groupingFunctions, + operators, + } = await import('./esql_documentation_sections'); + groups.push({ + label: i18n.translate('languageDocumentationPopover.esqlSections.initialSectionLabel', { + defaultMessage: 'ES|QL', + }), + items: [], + }); + groups.push( + sourceCommands, + processingCommands, + scalarFunctions, + aggregationFunctions, + groupingFunctions, + operators + ); + return { + groups, + initialSection, + }; +}; diff --git a/src/plugins/controls/public/services/embeddable/types.ts b/packages/kbn-language-documentation-popover/src/types.ts similarity index 68% rename from src/plugins/controls/public/services/embeddable/types.ts rename to packages/kbn-language-documentation-popover/src/types.ts index 917f03fb55e9b..863c00b94e21e 100644 --- a/src/plugins/controls/public/services/embeddable/types.ts +++ b/packages/kbn-language-documentation-popover/src/types.ts @@ -7,8 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; - -export interface ControlsEmbeddableService { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; +export interface LanguageDocumentationSections { + groups: Array<{ + label: string; + description?: string; + items: Array<{ label: string; description?: JSX.Element }>; + }>; + initialSection: JSX.Element; } diff --git a/packages/kbn-language-documentation-popover/src/utils/get_filtered_groups.test.tsx b/packages/kbn-language-documentation-popover/src/utils/get_filtered_groups.test.tsx new file mode 100644 index 0000000000000..9dfe443f10b0b --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/utils/get_filtered_groups.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import React from 'react'; +import { Markdown } from '@kbn/shared-ux-markdown'; +import { getFilteredGroups } from './get_filtered_groups'; + +describe('getFilteredGroups', () => { + const sections = { + groups: [ + { + label: 'Section one', + description: 'Section 1 description', + items: [], + }, + { + label: 'Section two', + items: [ + { + label: 'Section two item 1 blah blah', + description: ( + + ), + }, + { + label: 'Section two item 2', + description: ( + + ), + }, + ], + }, + { + label: 'Section three ', + items: [ + { + label: 'Section three item 1', + description: ( + + ), + }, + { + label: 'Section three item 2', + description: ( + + ), + }, + ], + }, + ], + initialSection: Here is the initial section, + }; + test('Should return the sections as it gets them if the search string is empty', () => { + const filteredSections = getFilteredGroups('', false, sections); + expect(filteredSections).toStrictEqual([ + ...sections.groups.map((group) => ({ ...group, options: group.items })), + ]); + }); + + test('Should return the 2 last sections as it gets them if the search string is empty and the numOfGroupsToOmit is set to 1', () => { + const filteredSections = getFilteredGroups('', false, sections, 1); + expect(filteredSections).toStrictEqual([ + ...sections.groups.slice(1).map((group) => ({ ...group, options: group.items })), + ]); + }); + + test('Should return the section two as it gets it if the search string is asking for this', () => { + const filteredSections = getFilteredGroups('tWo', false, sections); + expect(filteredSections).toStrictEqual([ + { ...sections.groups[1], options: sections.groups[1].items }, + ]); + }); + + test('Should return the section two filtered on the search string if it is allowed to search in description', () => { + const filteredSections = getFilteredGroups('Section two item 1 blah blah', true, sections); + expect(filteredSections).toStrictEqual([ + { ...sections.groups[1], options: [sections.groups[1].items[0]] }, + ]); + }); +}); diff --git a/packages/kbn-language-documentation-popover/src/utils/get_filtered_groups.ts b/packages/kbn-language-documentation-popover/src/utils/get_filtered_groups.ts new file mode 100644 index 0000000000000..2ac252bdad775 --- /dev/null +++ b/packages/kbn-language-documentation-popover/src/utils/get_filtered_groups.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import type { LanguageDocumentationSections } from '../types'; +import { elementToString } from './element_to_string'; + +export const getFilteredGroups = ( + searchText: string, + searchInDescription?: boolean, + sections?: LanguageDocumentationSections, + numOfGroupsToOmit?: number +) => { + const normalizedSearchText = searchText.trim().toLocaleLowerCase(); + return sections?.groups + .slice(numOfGroupsToOmit ?? 0) + .map((group) => { + const options = group.items.filter((helpItem) => { + return ( + !normalizedSearchText || + helpItem.label.toLocaleLowerCase().includes(normalizedSearchText) || + // Converting the JSX element to a string first + (searchInDescription && + elementToString(helpItem.description) + ?.toLocaleLowerCase() + .includes(normalizedSearchText)) + ); + }); + return { ...group, options }; + }) + .filter((group) => { + if (group.options.length > 0 || !normalizedSearchText) { + return true; + } + return group.label.toLocaleLowerCase().includes(normalizedSearchText); + }); +}; diff --git a/packages/kbn-language-documentation-popover/tsconfig.json b/packages/kbn-language-documentation-popover/tsconfig.json index 48da6397a6448..f613b6cb759aa 100644 --- a/packages/kbn-language-documentation-popover/tsconfig.json +++ b/packages/kbn-language-documentation-popover/tsconfig.json @@ -5,6 +5,7 @@ "types": [ "jest", "node", + "@emotion/react/types/css-prop", ] }, "include": [ diff --git a/packages/kbn-text-based-editor/README.md b/packages/kbn-text-based-editor/README.md index 2bb9ae5887f24..ae7846a58d207 100644 --- a/packages/kbn-text-based-editor/README.md +++ b/packages/kbn-text-based-editor/README.md @@ -14,7 +14,6 @@ In order to enable text based languages on your unified search bar add `textBase ## Languages supported -- SQL: based on the Elasticsearch sql api - ESQL: based on the Elastisearch esql api diff --git a/packages/kbn-text-based-editor/package.json b/packages/kbn-text-based-editor/package.json index 3eeb282f953c2..47d3d426b21f2 100644 --- a/packages/kbn-text-based-editor/package.json +++ b/packages/kbn-text-based-editor/package.json @@ -5,10 +5,5 @@ "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", "sideEffects": [ "*.scss" - ], - "scripts": { - "make:docs": "ts-node --transpileOnly scripts/generate_esql_docs.ts", - "postmake:docs": "yarn run lint:fix", - "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-text-based-editor/src/inline_documentation/generated" - } + ] } diff --git a/packages/kbn-text-based-editor/src/editor_footer/index.tsx b/packages/kbn-text-based-editor/src/editor_footer/index.tsx index fa60eb1a32f44..6468a2c08a8bc 100644 --- a/packages/kbn-text-based-editor/src/editor_footer/index.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer/index.tsx @@ -7,18 +7,25 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { memo, useState, useCallback, useEffect, useMemo } from 'react'; +import React, { memo, useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCode } from '@elastic/eui'; +import { + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiCode, + EuiButtonIcon, + EuiButtonEmpty, +} from '@elastic/eui'; import { Interpolation, Theme, css } from '@emotion/react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { - LanguageDocumentationPopover, - type LanguageDocumentationSections, + LanguageDocumentationInline, + LanguageDocumentationFlyout, } from '@kbn/language-documentation-popover'; import { getLimitFromESQLQuery } from '@kbn/esql-utils'; -import { type MonacoMessage, getDocumentationSections } from '../helpers'; +import { type MonacoMessage } from '../helpers'; import { ErrorsWarningsFooterPopover } from './errors_warnings_popover'; import { QueryHistoryAction, QueryHistory } from './query_history'; import { SubmitFeedbackComponent } from './feedback_component'; @@ -43,8 +50,6 @@ interface EditorFooterProps { updateQuery: (qs: string) => void; isHistoryOpen: boolean; setIsHistoryOpen: (status: boolean) => void; - isHelpMenuOpen: boolean; - setIsHelpMenuOpen: (status: boolean) => void; measuredContainerWidth: number; hideRunQueryText?: boolean; editorIsInline?: boolean; @@ -52,6 +57,7 @@ interface EditorFooterProps { hideTimeFilterInfo?: boolean; hideQueryHistory?: boolean; isInCompactMode?: boolean; + displayDocumentationAsFlyout?: boolean; } export const EditorFooter = memo(function EditorFooter({ @@ -71,18 +77,15 @@ export const EditorFooter = memo(function EditorFooter({ setIsHistoryOpen, hideQueryHistory, isInCompactMode, + displayDocumentationAsFlyout, measuredContainerWidth, code, - isHelpMenuOpen, - setIsHelpMenuOpen, }: EditorFooterProps) { const kibana = useKibana(); const { docLinks } = kibana.services; - const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false); + const [isLanguageComponentOpen, setIsLanguageComponentOpen] = useState(false); const [isWarningPopoverOpen, setIsWarningPopoverOpen] = useState(false); - const [documentationSections, setDocumentationSections] = - useState(); const onUpdateAndSubmit = useCallback( (qs: string) => { @@ -98,17 +101,17 @@ export const EditorFooter = memo(function EditorFooter({ [runQuery, updateQuery] ); - const limit = useMemo(() => getLimitFromESQLQuery(code), [code]); + const toggleHistoryComponent = useCallback(() => { + setIsHistoryOpen(!isHistoryOpen); + setIsLanguageComponentOpen(false); + }, [isHistoryOpen, setIsHistoryOpen]); - useEffect(() => { - async function getDocumentation() { - const sections = await getDocumentationSections('esql'); - setDocumentationSections(sections); - } - if (!documentationSections) { - getDocumentation(); - } - }, [documentationSections]); + const toggleLanguageComponent = useCallback(async () => { + setIsLanguageComponentOpen(!isLanguageComponentOpen); + setIsHistoryOpen(false); + }, [isLanguageComponentOpen, setIsHistoryOpen]); + + const limit = useMemo(() => getLimitFromESQLQuery(code), [code]); return ( )} - {documentationSections && !editorIsInline && ( - - + toggleLanguageComponent()} + css={css` + cursor: pointer; + `} + /> + - + )} @@ -318,34 +318,14 @@ export const EditorFooter = memo(function EditorFooter({ {!hideQueryHistory && ( setIsHistoryOpen(!isHistoryOpen)} + toggleHistory={toggleHistoryComponent} isHistoryOpen={isHistoryOpen} isSpaceReduced={true} /> )} - {documentationSections && ( - - - - )} + + + @@ -362,6 +342,11 @@ export const EditorFooter = memo(function EditorFooter({ /> )} + {isLanguageComponentOpen && editorIsInline && ( + + + + )} ); }); diff --git a/packages/kbn-text-based-editor/src/helpers.ts b/packages/kbn-text-based-editor/src/helpers.ts index 0ba6d5004606f..b1bb3aaf826a7 100644 --- a/packages/kbn-text-based-editor/src/helpers.ts +++ b/packages/kbn-text-based-editor/src/helpers.ts @@ -161,43 +161,6 @@ export const parseErrors = (errors: Error[], code: string): MonacoMessage[] => { }); }; -export const getDocumentationSections = async (language: string) => { - const groups: Array<{ - label: string; - description?: string; - items: Array<{ label: string; description?: JSX.Element }>; - }> = []; - if (language === 'esql') { - const { - sourceCommands, - processingCommands, - initialSection, - scalarFunctions, - aggregationFunctions, - groupingFunctions, - operators, - } = await import('./inline_documentation/esql_documentation_sections'); - groups.push({ - label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.esql', { - defaultMessage: 'ES|QL', - }), - items: [], - }); - groups.push( - sourceCommands, - processingCommands, - scalarFunctions, - aggregationFunctions, - groupingFunctions, - operators - ); - return { - groups, - initialSection, - }; - } -}; - export const getIndicesList = async (dataViews: DataViewsPublicPluginStart) => { const indices = await dataViews.getIndices({ showAllIndices: false, diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx index d8fe9512691a1..0855d0326263f 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.test.tsx @@ -16,21 +16,6 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { TextBasedLanguagesEditor } from './text_based_languages_editor'; import type { TextBasedLanguagesEditorProps } from './types'; import { ReactWrapper } from 'enzyme'; - -jest.mock('./helpers', () => { - const module = jest.requireActual('./helpers'); - return { - ...module, - getDocumentationSections: () => ({ - groups: [ - { - label: 'How it works', - items: [], - }, - ], - }), - }; -}); import { of } from 'rxjs'; describe('TextBasedLanguagesEditor', () => { @@ -133,9 +118,6 @@ describe('TextBasedLanguagesEditor', () => { expect( component!.find('[data-test-subj="TextBasedLangEditor-toggleWordWrap"]').length ).not.toBe(0); - expect(component!.find('[data-test-subj="TextBasedLangEditor-documentation"]').length).not.toBe( - 0 - ); }); it('should render the resize for the expanded code editor mode', async () => { @@ -156,6 +138,18 @@ describe('TextBasedLanguagesEditor', () => { expect(component.find('[data-test-subj="TextBasedLangEditor-run-query"]').length).not.toBe(0); }); + it('should render the doc icon if the displayDocumentationAsFlyout is true', async () => { + const newProps = { + ...props, + displayDocumentationAsFlyout: true, + editorIsInline: false, + }; + const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); + expect(component.find('[data-test-subj="TextBasedLangEditor-documentation"]').length).not.toBe( + 0 + ); + }); + it('should not render the run query text if the hideRunQueryText prop is set to true', async () => { const newProps = { ...props, diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index d6936bde6fcbe..2ebd5a42a966a 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -78,6 +78,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ hideTimeFilterInfo, hideQueryHistory, hasOutline, + displayDocumentationAsFlyout, }: TextBasedLanguagesEditorProps) { const popoverRef = useRef(null); const datePickerOpenStatusRef = useRef(false); @@ -108,7 +109,6 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const [isHistoryOpen, setIsHistoryOpen] = useState(false); const [isCodeEditorExpandedFocused, setIsCodeEditorExpandedFocused] = useState(false); - const [isLanguagePopoverOpen, setIsLanguagePopoverOpen] = useState(false); const [isQueryLoading, setIsQueryLoading] = useState(true); const [abortController, setAbortController] = useState(new AbortController()); @@ -738,8 +738,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ setIsHistoryOpen={toggleHistory} measuredContainerWidth={measuredEditorWidth} hideQueryHistory={hideHistoryComponent} - isHelpMenuOpen={isLanguagePopoverOpen} - setIsHelpMenuOpen={setIsLanguagePopoverOpen} + displayDocumentationAsFlyout={displayDocumentationAsFlyout} /> { chainingSystem: ControlGroupChainingSystem; - labelPosition: ControlStyle; // TODO: Rename this type to ControlLabelPosition + labelPosition: ControlLabelPosition; autoApplySelections: boolean; ignoreParentSettings?: ParentIgnoreSettings; @@ -50,7 +50,7 @@ export interface ControlGroupSerializedState ignoreParentSettingsJSON: string; // In runtime state, we refer to this property as `labelPosition`; // to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state - controlStyle: ControlStyle; + controlStyle: ControlLabelPosition; // In runtime state, we refer to the inverse of this property as `autoApplySelections` // to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state showApplySelections?: boolean; diff --git a/src/plugins/controls/common/index.ts b/src/plugins/controls/common/index.ts index c59e4c04ac1b0..dd9c56778bb68 100644 --- a/src/plugins/controls/common/index.ts +++ b/src/plugins/controls/common/index.ts @@ -8,7 +8,7 @@ */ export type { - ControlStyle, + ControlLabelPosition, ControlWidth, DefaultControlState, DefaultDataControlState, @@ -18,7 +18,7 @@ export type { export { DEFAULT_CONTROL_GROW, - DEFAULT_CONTROL_STYLE, + DEFAULT_CONTROL_LABEL_POSITION, DEFAULT_CONTROL_WIDTH, OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL, diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts index e5eccbcab5cf5..10d4a88553586 100644 --- a/src/plugins/controls/common/options_list/types.ts +++ b/src/plugins/controls/common/options_list/types.ts @@ -15,8 +15,6 @@ import { OptionsListSortingType } from './suggestions_sorting'; import { DefaultDataControlState } from '../types'; import { OptionsListSearchTechnique } from './suggestions_searching'; -export const OPTIONS_LIST_CONTROL = 'optionsListControl'; // TODO: Replace with OPTIONS_LIST_CONTROL_TYPE - /** * ---------------------------------------------------------------- * Options list state types diff --git a/src/plugins/controls/common/types.ts b/src/plugins/controls/common/types.ts index 34d4708b3e991..d3a6261aeb9da 100644 --- a/src/plugins/controls/common/types.ts +++ b/src/plugins/controls/common/types.ts @@ -8,7 +8,7 @@ */ export type ControlWidth = 'small' | 'medium' | 'large'; -export type ControlStyle = 'twoLine' | 'oneLine'; +export type ControlLabelPosition = 'twoLine' | 'oneLine'; export type TimeSlice = [number, number]; diff --git a/src/plugins/controls/jest_setup.ts b/src/plugins/controls/jest_setup.ts index 722f87562328e..04a52e3b6653f 100644 --- a/src/plugins/controls/jest_setup.ts +++ b/src/plugins/controls/jest_setup.ts @@ -8,8 +8,5 @@ */ // Start the services with stubs -import { pluginServices } from './public/services'; -import { registry } from './public/services/plugin_services.stub'; - -registry.start({}); -pluginServices.setRegistry(registry); +import { setStubKibanaServices } from './public/services/mocks'; +setStubKibanaServices(); diff --git a/src/plugins/controls/kibana.jsonc b/src/plugins/controls/kibana.jsonc index bd65ecc2d0b6f..add8c14ee3391 100644 --- a/src/plugins/controls/kibana.jsonc +++ b/src/plugins/controls/kibana.jsonc @@ -9,8 +9,6 @@ "browser": true, "requiredPlugins": [ "presentationUtil", - "kibanaReact", - "expressions", "embeddable", "dataViews", "data", @@ -18,6 +16,6 @@ "uiActions" ], "extraPublicDirs": ["common"], - "requiredBundles": ["kibanaUtils"] + "requiredBundles": [] } } diff --git a/src/plugins/controls/public/actions/clear_control_action.tsx b/src/plugins/controls/public/actions/clear_control_action.tsx index b7c2777473fd5..02347ace2fd8d 100644 --- a/src/plugins/controls/public/actions/clear_control_action.tsx +++ b/src/plugins/controls/public/actions/clear_control_action.tsx @@ -11,42 +11,10 @@ import React, { SyntheticEvent } from 'react'; import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { - apiIsPresentationContainer, - type PresentationContainer, -} from '@kbn/presentation-containers'; -import { - apiCanAccessViewMode, - apiHasParentApi, - apiHasType, - apiHasUniqueId, - apiIsOfType, - type EmbeddableApiContext, - type HasParentApi, - type HasType, - type HasUniqueId, -} from '@kbn/presentation-publishing'; -import { type Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { EmbeddableApiContext, HasUniqueId } from '@kbn/presentation-publishing'; +import { IncompatibleActionError, type Action } from '@kbn/ui-actions-plugin/public'; import { ACTION_CLEAR_CONTROL } from '.'; -import { CONTROL_GROUP_TYPE } from '..'; -import { isClearableControl, type CanClearSelections } from '../types'; - -export type ClearControlActionApi = HasType & - HasUniqueId & - CanClearSelections & - HasParentApi; - -const isApiCompatible = (api: unknown | null): api is ClearControlActionApi => - Boolean( - apiHasType(api) && - apiHasUniqueId(api) && - isClearableControl(api) && - apiHasParentApi(api) && - apiCanAccessViewMode(api.parentApi) && - apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) && - apiIsPresentationContainer(api.parentApi) - ); export class ClearControlAction implements Action { public readonly type = ACTION_CLEAR_CONTROL; @@ -56,12 +24,10 @@ export class ClearControlAction implements Action { constructor() {} public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => { - if (!isApiCompatible(context.embeddable)) throw new IncompatibleActionError(); - return ( ) => { @@ -75,23 +41,24 @@ export class ClearControlAction implements Action { }; public getDisplayName({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); return i18n.translate('controls.controlGroup.floatingActions.clearTitle', { defaultMessage: 'Clear', }); } public getIconType({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); return 'eraser'; } public async isCompatible({ embeddable }: EmbeddableApiContext) { - return isApiCompatible(embeddable); + const { isCompatible } = await import('./clear_control_action_compatibility_check'); + return isCompatible(embeddable); } public async execute({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); + const { compatibilityCheck } = await import('./clear_control_action_compatibility_check'); + if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError(); + embeddable.clearSelections(); } } diff --git a/src/plugins/controls/public/actions/clear_control_action_compatibility_check.ts b/src/plugins/controls/public/actions/clear_control_action_compatibility_check.ts new file mode 100644 index 0000000000000..f04cb91bc9a3a --- /dev/null +++ b/src/plugins/controls/public/actions/clear_control_action_compatibility_check.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { PresentationContainer, apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { + HasParentApi, + HasType, + HasUniqueId, + apiCanAccessViewMode, + apiHasParentApi, + apiHasType, + apiHasUniqueId, + apiIsOfType, +} from '@kbn/presentation-publishing'; +import { CONTROL_GROUP_TYPE } from '../../common'; +import { isClearableControl, type CanClearSelections } from '../types'; + +type ClearControlActionApi = HasType & + HasUniqueId & + CanClearSelections & + HasParentApi; + +export const compatibilityCheck = (api: unknown | null): api is ClearControlActionApi => + Boolean( + apiHasType(api) && + apiHasUniqueId(api) && + isClearableControl(api) && + apiHasParentApi(api) && + apiCanAccessViewMode(api.parentApi) && + apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) && + apiIsPresentationContainer(api.parentApi) + ); + +export function isCompatible(api: unknown) { + return compatibilityCheck(api); +} diff --git a/src/plugins/controls/public/actions/delete_control_action.test.tsx b/src/plugins/controls/public/actions/delete_control_action.test.tsx index 65be8a65ecd6f..c158d743f69ae 100644 --- a/src/plugins/controls/public/actions/delete_control_action.test.tsx +++ b/src/plugins/controls/public/actions/delete_control_action.test.tsx @@ -9,22 +9,15 @@ import { BehaviorSubject } from 'rxjs'; -import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { ViewMode } from '@kbn/presentation-publishing'; - import { getOptionsListControlFactory } from '../react_controls/controls/data_controls/options_list_control/get_options_list_control_factory'; import { OptionsListControlApi } from '../react_controls/controls/data_controls/options_list_control/types'; import { getMockedBuildApi, getMockedControlGroupApi, } from '../react_controls/controls/mocks/control_mocks'; -import { pluginServices } from '../services'; import { DeleteControlAction } from './delete_control_action'; - -const mockDataViews = dataViewPluginMocks.createStartContract(); -const mockCore = coreMock.createStart(); +import { coreServices } from '../services/kibana_services'; const dashboardApi = { viewMode: new BehaviorSubject('view'), @@ -38,11 +31,7 @@ const controlGroupApi = getMockedControlGroupApi(dashboardApi, { let controlApi: OptionsListControlApi; beforeAll(async () => { - const controlFactory = getOptionsListControlFactory({ - core: mockCore, - data: dataPluginMock.createStartContract(), - dataViews: mockDataViews, - }); + const controlFactory = getOptionsListControlFactory(); const uuid = 'testControl'; const control = await controlFactory.buildControl( @@ -72,7 +61,7 @@ test('Execute throws an error when called with an embeddable not in a parent', a describe('Execute should open a confirm modal', () => { test('Canceling modal will keep control', async () => { const spyOn = jest.fn().mockResolvedValue(false); - pluginServices.getServices().overlays.openConfirm = spyOn; + coreServices.overlays.openConfirm = spyOn; const deleteControlAction = new DeleteControlAction(); await deleteControlAction.execute({ embeddable: controlApi }); @@ -83,7 +72,7 @@ describe('Execute should open a confirm modal', () => { test('Confirming modal will delete control', async () => { const spyOn = jest.fn().mockResolvedValue(true); - pluginServices.getServices().overlays.openConfirm = spyOn; + coreServices.overlays.openConfirm = spyOn; const deleteControlAction = new DeleteControlAction(); await deleteControlAction.execute({ embeddable: controlApi }); diff --git a/src/plugins/controls/public/actions/delete_control_action.tsx b/src/plugins/controls/public/actions/delete_control_action.tsx index 45a7a20385627..7ee55ddd3da69 100644 --- a/src/plugins/controls/public/actions/delete_control_action.tsx +++ b/src/plugins/controls/public/actions/delete_control_action.tsx @@ -10,65 +10,25 @@ import React from 'react'; import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; -import { - apiIsPresentationContainer, - type PresentationContainer, -} from '@kbn/presentation-containers'; -import { - apiCanAccessViewMode, - apiHasParentApi, - apiHasType, - apiHasUniqueId, - apiIsOfType, - getInheritedViewMode, - type EmbeddableApiContext, - type HasParentApi, - type HasType, - type HasUniqueId, - type PublishesViewMode, -} from '@kbn/presentation-publishing'; +import type { HasUniqueId, EmbeddableApiContext } from '@kbn/presentation-publishing'; import { IncompatibleActionError, type Action } from '@kbn/ui-actions-plugin/public'; import { ACTION_DELETE_CONTROL } from '.'; -import { CONTROL_GROUP_TYPE } from '..'; -import { pluginServices } from '../services'; - -export type DeleteControlActionApi = HasType & - HasUniqueId & - HasParentApi; - -const isApiCompatible = (api: unknown | null): api is DeleteControlActionApi => - Boolean( - apiHasType(api) && - apiHasUniqueId(api) && - apiHasParentApi(api) && - apiCanAccessViewMode(api.parentApi) && - apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) && - apiIsPresentationContainer(api.parentApi) - ); +import { coreServices } from '../services/kibana_services'; export class DeleteControlAction implements Action { public readonly type = ACTION_DELETE_CONTROL; public readonly id = ACTION_DELETE_CONTROL; public order = 100; // should always be last - private openConfirm; - - constructor() { - ({ - overlays: { openConfirm: this.openConfirm }, - } = pluginServices.getServices()); - } + constructor() {} public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => { - if (!isApiCompatible(context.embeddable)) throw new IncompatibleActionError(); - return ( this.execute(context)} @@ -79,46 +39,46 @@ export class DeleteControlAction implements Action { }; public getDisplayName({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); return i18n.translate('controls.controlGroup.floatingActions.removeTitle', { defaultMessage: 'Delete', }); } public getIconType({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); return 'trash'; } public async isCompatible({ embeddable }: EmbeddableApiContext) { - return ( - isApiCompatible(embeddable) && getInheritedViewMode(embeddable.parentApi) === ViewMode.EDIT - ); + const { isCompatible } = await import('./delete_control_action_compatibility_check'); + return isCompatible(embeddable); } public async execute({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); + const { compatibilityCheck } = await import('./delete_control_action_compatibility_check'); + if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError(); - this.openConfirm( - i18n.translate('controls.controlGroup.management.delete.sub', { - defaultMessage: 'Controls are not recoverable once removed.', - }), - { - confirmButtonText: i18n.translate('controls.controlGroup.management.delete.confirm', { - defaultMessage: 'Delete', - }), - cancelButtonText: i18n.translate('controls.controlGroup.management.delete.cancel', { - defaultMessage: 'Cancel', + coreServices.overlays + .openConfirm( + i18n.translate('controls.controlGroup.management.delete.sub', { + defaultMessage: 'Controls are not recoverable once removed.', }), - title: i18n.translate('controls.controlGroup.management.delete.deleteTitle', { - defaultMessage: 'Delete control?', - }), - buttonColor: 'danger', - } - ).then((confirmed) => { - if (confirmed) { - embeddable.parentApi.removePanel(embeddable.uuid); - } - }); + { + confirmButtonText: i18n.translate('controls.controlGroup.management.delete.confirm', { + defaultMessage: 'Delete', + }), + cancelButtonText: i18n.translate('controls.controlGroup.management.delete.cancel', { + defaultMessage: 'Cancel', + }), + title: i18n.translate('controls.controlGroup.management.delete.deleteTitle', { + defaultMessage: 'Delete control?', + }), + buttonColor: 'danger', + } + ) + .then((confirmed) => { + if (confirmed) { + embeddable.parentApi.removePanel(embeddable.uuid); + } + }); } } diff --git a/src/plugins/controls/public/actions/delete_control_action_compatibility_check.ts b/src/plugins/controls/public/actions/delete_control_action_compatibility_check.ts new file mode 100644 index 0000000000000..a09b3448b2fc1 --- /dev/null +++ b/src/plugins/controls/public/actions/delete_control_action_compatibility_check.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { PresentationContainer, apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { + HasParentApi, + HasType, + HasUniqueId, + PublishesViewMode, + apiCanAccessViewMode, + apiHasParentApi, + apiHasType, + apiHasUniqueId, + apiIsOfType, + getInheritedViewMode, +} from '@kbn/presentation-publishing'; +import { CONTROL_GROUP_TYPE } from '../../common'; + +type DeleteControlActionApi = HasType & + HasUniqueId & + HasParentApi; + +export const compatibilityCheck = (api: unknown | null): api is DeleteControlActionApi => + Boolean( + apiHasType(api) && + apiHasUniqueId(api) && + apiHasParentApi(api) && + apiCanAccessViewMode(api.parentApi) && + apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) && + apiIsPresentationContainer(api.parentApi) + ); + +export function isCompatible(api: unknown) { + return compatibilityCheck(api) && getInheritedViewMode(api.parentApi) === ViewMode.EDIT; +} diff --git a/src/plugins/controls/public/actions/edit_control_action.test.tsx b/src/plugins/controls/public/actions/edit_control_action.test.tsx index 3c28feb907421..b1c24d779aaf6 100644 --- a/src/plugins/controls/public/actions/edit_control_action.test.tsx +++ b/src/plugins/controls/public/actions/edit_control_action.test.tsx @@ -9,9 +9,6 @@ import { BehaviorSubject } from 'rxjs'; -import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import dateMath from '@kbn/datemath'; import type { TimeRange } from '@kbn/es-query'; import type { ViewMode } from '@kbn/presentation-publishing'; @@ -23,12 +20,10 @@ import { getMockedControlGroupApi, } from '../react_controls/controls/mocks/control_mocks'; import { getTimesliderControlFactory } from '../react_controls/controls/timeslider_control/get_timeslider_control_factory'; +import { dataService } from '../services/kibana_services'; import { EditControlAction } from './edit_control_action'; -const mockDataViews = dataViewPluginMocks.createStartContract(); -const mockCore = coreMock.createStart(); -const dataStartServiceMock = dataPluginMock.createStartContract(); -dataStartServiceMock.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => { +dataService.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => { const now = new Date(); return { min: dateMath.parse(timeRange.from, { forceNow: now }), @@ -48,11 +43,7 @@ const controlGroupApi = getMockedControlGroupApi(dashboardApi, { let optionsListApi: OptionsListControlApi; beforeAll(async () => { - const controlFactory = getOptionsListControlFactory({ - core: mockCore, - data: dataStartServiceMock, - dataViews: mockDataViews, - }); + const controlFactory = getOptionsListControlFactory(); const optionsListUuid = 'optionsListControl'; const optionsListControl = await controlFactory.buildControl( @@ -73,10 +64,7 @@ beforeAll(async () => { describe('Incompatible embeddables', () => { test('Action is incompatible with embeddables that are not editable', async () => { - const timeSliderFactory = getTimesliderControlFactory({ - core: mockCore, - data: dataStartServiceMock, - }); + const timeSliderFactory = getTimesliderControlFactory(); const timeSliderUuid = 'timeSliderControl'; const timeSliderControl = await timeSliderFactory.buildControl( {}, diff --git a/src/plugins/controls/public/index.ts b/src/plugins/controls/public/index.ts index 6a490248b8929..6c7a548cb091d 100644 --- a/src/plugins/controls/public/index.ts +++ b/src/plugins/controls/public/index.ts @@ -21,7 +21,6 @@ export { ACTION_CLEAR_CONTROL, ACTION_DELETE_CONTROL, ACTION_EDIT_CONTROL } from export type { DataControlApi, DataControlFactory, - DataControlServices, } from './react_controls/controls/data_controls/types'; export { diff --git a/src/plugins/controls/public/plugin.ts b/src/plugins/controls/public/plugin.ts index 039b960bfc3c5..c6e1a2873b169 100644 --- a/src/plugins/controls/public/plugin.ts +++ b/src/plugins/controls/public/plugin.ts @@ -10,63 +10,36 @@ import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { PANEL_HOVER_TRIGGER } from '@kbn/embeddable-plugin/public'; +import { ClearControlAction } from './actions/clear_control_action'; +import { DeleteControlAction } from './actions/delete_control_action'; +import { EditControlAction } from './actions/edit_control_action'; import { registerControlGroupEmbeddable } from './react_controls/control_group/register_control_group_embeddable'; import { registerOptionsListControl } from './react_controls/controls/data_controls/options_list_control/register_options_list_control'; import { registerRangeSliderControl } from './react_controls/controls/data_controls/range_slider/register_range_slider_control'; import { registerTimeSliderControl } from './react_controls/controls/timeslider_control/register_timeslider_control'; -import { controlsService } from './services/controls/controls_service'; -import type { - ControlsPluginSetup, - ControlsPluginSetupDeps, - ControlsPluginStart, - ControlsPluginStartDeps, -} from './types'; +import { setKibanaServices, untilPluginStartServicesReady } from './services/kibana_services'; +import type { ControlsPluginSetupDeps, ControlsPluginStartDeps } from './types'; export class ControlsPlugin - implements - Plugin< - ControlsPluginSetup, - ControlsPluginStart, - ControlsPluginSetupDeps, - ControlsPluginStartDeps - > + implements Plugin { - private async startControlsKibanaServices( - coreStart: CoreStart, - startPlugins: ControlsPluginStartDeps - ) { - const { registry, pluginServices } = await import('./services/plugin_services'); - pluginServices.setRegistry(registry.start({ coreStart, startPlugins })); - } - public setup( - _coreSetup: CoreSetup, + _coreSetup: CoreSetup, _setupPlugins: ControlsPluginSetupDeps - ): ControlsPluginSetup { - const { registerControlFactory } = controlsService; + ) { const { embeddable } = _setupPlugins; - registerControlGroupEmbeddable(_coreSetup, embeddable); - registerOptionsListControl(_coreSetup); - registerRangeSliderControl(_coreSetup); - registerTimeSliderControl(_coreSetup); - - return { - registerControlFactory, - }; + registerControlGroupEmbeddable(embeddable); + registerOptionsListControl(); + registerRangeSliderControl(); + registerTimeSliderControl(); } - public start(coreStart: CoreStart, startPlugins: ControlsPluginStartDeps): ControlsPluginStart { - this.startControlsKibanaServices(coreStart, startPlugins).then(async () => { - const { uiActions } = startPlugins; - - const [{ DeleteControlAction }, { EditControlAction }, { ClearControlAction }] = - await Promise.all([ - import('./actions/delete_control_action'), - import('./actions/edit_control_action'), - import('./actions/clear_control_action'), - ]); + public start(coreStart: CoreStart, startPlugins: ControlsPluginStartDeps) { + const { uiActions } = startPlugins; + setKibanaServices(coreStart, startPlugins); + untilPluginStartServicesReady().then(() => { const deleteControlAction = new DeleteControlAction(); uiActions.registerAction(deleteControlAction); uiActions.attachAction(PANEL_HOVER_TRIGGER, deleteControlAction.id); @@ -79,12 +52,6 @@ export class ControlsPlugin uiActions.registerAction(clearControlAction); uiActions.attachAction(PANEL_HOVER_TRIGGER, clearControlAction.id); }); - - const { getControlFactory, getAllControlTypes } = controlsService; - return { - getControlFactory, - getAllControlTypes, - }; } public stop() {} diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_error.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_error.tsx index 52b9e7d9b806a..2ef6b06faeedd 100644 --- a/src/plugins/controls/public/react_controls/control_group/components/control_error.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_error.tsx @@ -13,8 +13,6 @@ import { EuiButtonEmpty, EuiPopover } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { Markdown } from '@kbn/shared-ux-markdown'; -/** TODO: This file is duplicated from the controls plugin to avoid exporting it */ - interface ControlErrorProps { error: Error | string; } diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_group.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_group.tsx index 9c962113d1a7f..54e778684806a 100644 --- a/src/plugins/controls/public/react_controls/control_group/components/control_group.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_group.tsx @@ -30,7 +30,7 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiToolTip } from ' import { css } from '@emotion/react'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; -import type { ControlStyle } from '../../../../common'; +import type { ControlLabelPosition } from '../../../../common'; import type { DefaultControlApi } from '../../controls/types'; import { ControlGroupStrings } from '../control_group_strings'; import { ControlsInOrder } from '../init_controls_manager'; @@ -49,7 +49,7 @@ interface Props { setControlApi: (uuid: string, controlApi: DefaultControlApi) => void; }; hasUnappliedSelections: boolean; - labelPosition: ControlStyle; + labelPosition: ControlLabelPosition; } export function ControlGroup({ diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx index 79d0312b29537..b3705106afe2c 100644 --- a/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.test.tsx @@ -15,8 +15,8 @@ import { render } from '@testing-library/react'; import { ControlGroupApi } from '../../..'; import { ControlGroupChainingSystem, - ControlStyle, - DEFAULT_CONTROL_STYLE, + ControlLabelPosition, + DEFAULT_CONTROL_LABEL_POSITION, ParentIgnoreSettings, } from '../../../../common'; import { DefaultControlApi } from '../../controls/types'; @@ -33,7 +33,7 @@ describe('render', () => { onDeleteAll: () => {}, stateManager: { chainingSystem: new BehaviorSubject('HIERARCHICAL'), - labelPosition: new BehaviorSubject(DEFAULT_CONTROL_STYLE), + labelPosition: new BehaviorSubject(DEFAULT_CONTROL_LABEL_POSITION), autoApplySelections: new BehaviorSubject(true), ignoreParentSettings: new BehaviorSubject(undefined), }, diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx index f908a557366fa..c4e7dc61476ba 100644 --- a/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_group_editor.tsx @@ -27,7 +27,7 @@ import { } from '@elastic/eui'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; -import type { ControlStyle, ParentIgnoreSettings } from '../../../../common'; +import type { ControlLabelPosition, ParentIgnoreSettings } from '../../../../common'; import { CONTROL_LAYOUT_OPTIONS } from '../../controls/data_controls/editor_constants'; import type { ControlStateManager } from '../../controls/types'; import { ControlGroupStrings } from '../control_group_strings'; @@ -86,7 +86,7 @@ export const ControlGroupEditor = ({ onCancel, onSave, onDeleteAll, stateManager idSelected={selectedLabelPosition} legend={ControlGroupStrings.management.labelPosition.getLabelPositionLegend()} onChange={(newPosition: string) => { - stateManager.labelPosition.next(newPosition as ControlStyle); + stateManager.labelPosition.next(newPosition as ControlLabelPosition); }} /> diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx index bbf8e8127813e..365c896bb908e 100644 --- a/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_panel.test.tsx @@ -14,7 +14,7 @@ import { pluginServices as presentationUtilPluginServices } from '@kbn/presentat import { registry as presentationUtilServicesRegistry } from '@kbn/presentation-util-plugin/public/services/plugin_services.story'; import { render, waitFor } from '@testing-library/react'; -import type { ControlStyle, ControlWidth } from '../../../../common'; +import type { ControlLabelPosition, ControlWidth } from '../../../../common'; import { ControlPanel } from './control_panel'; describe('render', () => { @@ -74,7 +74,7 @@ describe('render', () => { mockApi = { uuid: 'control1', parentApi: { - labelPosition: new BehaviorSubject('oneLine'), + labelPosition: new BehaviorSubject('oneLine'), }, }; const controlPanel = render(); @@ -92,7 +92,7 @@ describe('render', () => { mockApi = { uuid: 'control1', parentApi: { - labelPosition: new BehaviorSubject('twoLine'), + labelPosition: new BehaviorSubject('twoLine'), }, }; const controlPanel = render(); diff --git a/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx b/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx index 6c3e8d10c3c66..77da1480eb494 100644 --- a/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx +++ b/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx @@ -11,9 +11,7 @@ import fastIsEqual from 'fast-deep-equal'; import React, { useEffect } from 'react'; import { BehaviorSubject } from 'rxjs'; -import { CoreStart } from '@kbn/core/public'; import { DataView } from '@kbn/data-views-plugin/common'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; import { @@ -31,11 +29,11 @@ import type { ControlGroupChainingSystem, ControlGroupRuntimeState, ControlGroupSerializedState, + ControlLabelPosition, ControlPanelsState, - ControlStyle, ParentIgnoreSettings, } from '../../../common'; -import { CONTROL_GROUP_TYPE, DEFAULT_CONTROL_STYLE } from '../../../common'; +import { CONTROL_GROUP_TYPE, DEFAULT_CONTROL_LABEL_POSITION } from '../../../common'; import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor'; import { ControlGroup } from './components/control_group'; import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch'; @@ -45,13 +43,11 @@ import { openEditControlGroupFlyout } from './open_edit_control_group_flyout'; import { initSelectionsManager } from './selections_manager'; import type { ControlGroupApi } from './types'; import { deserializeControlGroup } from './utils/serialization_utils'; +import { coreServices, dataViewsService } from '../../services/kibana_services'; const DEFAULT_CHAINING_SYSTEM = 'HIERARCHICAL'; -export const getControlGroupEmbeddableFactory = (services: { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; -}) => { +export const getControlGroupEmbeddableFactory = () => { const controlGroupEmbeddableFactory: ReactEmbeddableFactory< ControlGroupSerializedState, ControlGroupRuntimeState, @@ -75,7 +71,7 @@ export const getControlGroupEmbeddableFactory = (services: { } = initialRuntimeState; const autoApplySelections$ = new BehaviorSubject(autoApplySelections); - const defaultDataViewId = await services.dataViews.getDefaultId(); + const defaultDataViewId = await dataViewsService.getDefaultId(); const lastSavedControlsState$ = new BehaviorSubject( lastSavedRuntimeState.initialChildControlState ); @@ -94,15 +90,12 @@ export const getControlGroupEmbeddableFactory = (services: { const ignoreParentSettings$ = new BehaviorSubject( ignoreParentSettings ); - const labelPosition$ = new BehaviorSubject( // TODO: Rename `ControlStyle` - initialLabelPosition ?? DEFAULT_CONTROL_STYLE // TODO: Rename `DEFAULT_CONTROL_STYLE` + const labelPosition$ = new BehaviorSubject( + initialLabelPosition ?? DEFAULT_CONTROL_LABEL_POSITION ); const allowExpensiveQueries$ = new BehaviorSubject(true); const disabledActionIds$ = new BehaviorSubject(undefined); - /** TODO: Handle loading; loading should be true if any child is loading */ - const dataLoading$ = new BehaviorSubject(false); - const unsavedChanges = initializeControlGroupUnsavedChanges( selectionsManager.applySelections, controlsManager.api.children$, @@ -122,7 +115,10 @@ export const getControlGroupEmbeddableFactory = (services: { (next: ParentIgnoreSettings | undefined) => ignoreParentSettings$.next(next), fastIsEqual, ], - labelPosition: [labelPosition$, (next: ControlStyle) => labelPosition$.next(next)], + labelPosition: [ + labelPosition$, + (next: ControlLabelPosition) => labelPosition$.next(next), + ], }, controlsManager.snapshotControlsRuntimeState, controlsManager.resetControlsUnsavedChanges, @@ -157,18 +153,13 @@ export const getControlGroupEmbeddableFactory = (services: { initialChildControlState: controlsManager.snapshotControlsRuntimeState(), }; }, - dataLoading: dataLoading$, onEdit: async () => { - openEditControlGroupFlyout( - api, - { - chainingSystem: chainingSystem$, - labelPosition: labelPosition$, - autoApplySelections: autoApplySelections$, - ignoreParentSettings: ignoreParentSettings$, - }, - { core: services.core } - ); + openEditControlGroupFlyout(api, { + chainingSystem: chainingSystem$, + labelPosition: labelPosition$, + autoApplySelections: autoApplySelections$, + ignoreParentSettings: ignoreParentSettings$, + }); }, isEditingEnabled: () => true, openAddDataControlFlyout: (settings) => { @@ -193,7 +184,6 @@ export const getControlGroupEmbeddableFactory = (services: { settings?.onSave?.(); }, controlGroupApi: api, - services, }); }, serializeState: () => { @@ -201,7 +191,7 @@ export const getControlGroupEmbeddableFactory = (services: { return { rawState: { chainingSystem: chainingSystem$.getValue(), - controlStyle: labelPosition$.getValue(), // Rename "labelPosition" to "controlStyle" + controlStyle: labelPosition$.getValue(), showApplySelections: !autoApplySelections$.getValue(), ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()), panelsJSON, @@ -265,10 +255,9 @@ export const getControlGroupEmbeddableFactory = (services: { /** Fetch the allowExpensiveQuries setting for the children to use if necessary */ const fetchAllowExpensiveQueries = async () => { try { - const { allowExpensiveQueries } = await services.core.http.get<{ + const { allowExpensiveQueries } = await coreServices.http.get<{ allowExpensiveQueries: boolean; - // TODO: Rename this route as part of https://github.com/elastic/kibana/issues/174961 - }>('/internal/controls/optionsList/getExpensiveQueriesSetting', { + }>('/internal/controls/getExpensiveQueriesSetting', { version: '1', }); if (!allowExpensiveQueries) { diff --git a/src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx b/src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx index 5e7026282123a..5e7baf1f73e5d 100644 --- a/src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx +++ b/src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx @@ -8,7 +8,6 @@ */ import { OverlayRef } from '@kbn/core-mount-utils-browser'; -import { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { tracksOverlays } from '@kbn/presentation-containers'; import { apiHasParentApi } from '@kbn/presentation-publishing'; @@ -19,13 +18,11 @@ import { BehaviorSubject } from 'rxjs'; import { ControlStateManager } from '../controls/types'; import { ControlGroupEditor } from './components/control_group_editor'; import { ControlGroupApi, ControlGroupEditorState } from './types'; +import { coreServices } from '../../services/kibana_services'; export const openEditControlGroupFlyout = ( controlGroupApi: ControlGroupApi, - stateManager: ControlStateManager, - services: { - core: CoreStart; - } + stateManager: ControlStateManager ) => { /** * Duplicate all state into a new manager because we do not want to actually apply the changes @@ -50,7 +47,7 @@ export const openEditControlGroupFlyout = ( }; const onDeleteAll = (ref: OverlayRef) => { - services.core.overlays + coreServices.overlays .openConfirm( i18n.translate('controls.controlGroup.management.delete.sub', { defaultMessage: 'Controls are not recoverable once removed.', @@ -77,7 +74,7 @@ export const openEditControlGroupFlyout = ( }); }; - const overlay = services.core.overlays.openFlyout( + const overlay = coreServices.overlays.openFlyout( toMountPoint( closeOverlay(overlay)} />, { - theme: services.core.theme, - i18n: services.core.i18n, + theme: coreServices.theme, + i18n: coreServices.i18n, } ), { diff --git a/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.ts b/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.ts index 513633e46a875..a64faa63e8efc 100644 --- a/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.ts +++ b/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.ts @@ -7,23 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { CoreSetup } from '@kbn/core/public'; import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; -import type { ControlsPluginStartDeps } from '../../types'; import { CONTROL_GROUP_TYPE } from '../../../common'; +import { untilPluginStartServicesReady } from '../../services/kibana_services'; -export function registerControlGroupEmbeddable( - coreSetup: CoreSetup, - embeddableSetup: EmbeddableSetup -) { +export function registerControlGroupEmbeddable(embeddableSetup: EmbeddableSetup) { embeddableSetup.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => { - const [{ getControlGroupEmbeddableFactory }, [coreStart, depsStart]] = await Promise.all([ + const [{ getControlGroupEmbeddableFactory }] = await Promise.all([ import('./get_control_group_factory'), - coreSetup.getStartServices(), + untilPluginStartServicesReady(), ]); - return getControlGroupEmbeddableFactory({ - core: coreStart, - dataViews: depsStart.data.dataViews, - }); + return getControlGroupEmbeddableFactory(); }); } diff --git a/src/plugins/controls/public/react_controls/control_group/types.ts b/src/plugins/controls/public/react_controls/control_group/types.ts index cb09cac975e7a..37f9f40c4079f 100644 --- a/src/plugins/controls/public/react_controls/control_group/types.ts +++ b/src/plugins/controls/public/react_controls/control_group/types.ts @@ -19,7 +19,6 @@ import { import { HasEditCapabilities, HasParentApi, - PublishesDataLoading, PublishesDisabledActionIds, PublishesFilters, PublishesTimeslice, @@ -35,8 +34,8 @@ import { ControlGroupEditorConfig, ControlGroupRuntimeState, ControlGroupSerializedState, + ControlLabelPosition, ControlPanelState, - ControlStyle, DefaultControlState, ParentIgnoreSettings, } from '../../../common'; @@ -54,7 +53,6 @@ export type ControlGroupApi = PresentationContainer & PublishesDataViews & HasSerializedChildState & HasEditCapabilities & - PublishesDataLoading & Pick, 'unsavedChanges'> & PublishesTimeslice & PublishesDisabledActionIds & @@ -62,7 +60,7 @@ export type ControlGroupApi = PresentationContainer & allowExpensiveQueries$: PublishingSubject; autoApplySelections$: PublishingSubject; ignoreParentSettings$: PublishingSubject; - labelPosition: PublishingSubject; + labelPosition: PublishingSubject; asyncResetUnsavedChanges: () => Promise; controlFetch$: (controlUuid: string) => Observable; diff --git a/src/plugins/controls/public/react_controls/control_group/utils/control_group_state_builder.ts b/src/plugins/controls/public/react_controls/control_group/utils/control_group_state_builder.ts index 91e5379416c5e..1c051e58af46f 100644 --- a/src/plugins/controls/public/react_controls/control_group/utils/control_group_state_builder.ts +++ b/src/plugins/controls/public/react_controls/control_group/utils/control_group_state_builder.ts @@ -13,11 +13,12 @@ import { OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL, TIME_SLIDER_CONTROL, + type ControlGroupRuntimeState, + type ControlPanelsState, type DefaultDataControlState, } from '../../../../common'; -import { type ControlGroupRuntimeState, type ControlPanelsState } from '../../../../common'; import type { OptionsListControlState } from '../../../../common/options_list'; -import { pluginServices } from '../../../services'; +import { dataViewsService } from '../../../services/kibana_services'; import { getDataControlFieldRegistry } from '../../controls/data_controls/data_control_editor_utils'; import type { RangesliderControlState } from '../../controls/data_controls/range_slider/types'; @@ -82,7 +83,7 @@ export const controlGroupStateBuilder = { }; async function getCompatibleControlType(dataViewId: string, fieldName: string) { - const dataView = await pluginServices.getServices().dataViews.get(dataViewId); + const dataView = await dataViewsService.get(dataViewId); const fieldRegistry = await getDataControlFieldRegistry(dataView); const field = fieldRegistry[fieldName]; if (field.compatibleControlTypes.length === 0) { diff --git a/src/plugins/controls/public/react_controls/control_group/utils/initialization_utils.ts b/src/plugins/controls/public/react_controls/control_group/utils/initialization_utils.ts index 8bd19c3d6478c..ef81b4e30b361 100644 --- a/src/plugins/controls/public/react_controls/control_group/utils/initialization_utils.ts +++ b/src/plugins/controls/public/react_controls/control_group/utils/initialization_utils.ts @@ -7,11 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { type ControlGroupRuntimeState, DEFAULT_CONTROL_STYLE } from '../../../../common'; +import { DEFAULT_CONTROL_LABEL_POSITION, type ControlGroupRuntimeState } from '../../../../common'; export const getDefaultControlGroupRuntimeState = (): ControlGroupRuntimeState => ({ initialChildControlState: {}, - labelPosition: DEFAULT_CONTROL_STYLE, + labelPosition: DEFAULT_CONTROL_LABEL_POSITION, chainingSystem: 'HIERARCHICAL', autoApplySelections: true, ignoreParentSettings: { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx index 5f8f17c57bce5..8d8385d603fb3 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx @@ -12,7 +12,6 @@ import { BehaviorSubject } from 'rxjs'; import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { stubFieldSpecMap } from '@kbn/data-views-plugin/common/field.stub'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { TimeRange } from '@kbn/es-query'; import { I18nProvider } from '@kbn/i18n-react'; import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react'; @@ -22,6 +21,7 @@ import { DEFAULT_CONTROL_WIDTH, type DefaultDataControlState, } from '../../../../common'; +import { dataViewsService } from '../../../services/kibana_services'; import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; import type { ControlGroupApi } from '../../control_group/types'; import type { ControlFactory } from '../types'; @@ -39,7 +39,6 @@ jest.mock('../../control_factory_registry', () => ({ getControlFactory: jest.fn(), })); -const mockDataViews = dataViewPluginMocks.createStartContract(); const mockDataView = createStubDataView({ spec: { id: 'logstash-*', @@ -58,7 +57,6 @@ const mockDataView = createStubDataView({ timeFieldName: '@timestamp', }, }); -mockDataViews.get = jest.fn().mockResolvedValue(mockDataView); const dashboardApi = { timeRange$: new BehaviorSubject(undefined), @@ -82,7 +80,7 @@ describe('Data control editor', () => { controlType?: string; initialDefaultPanelTitle?: string; }) => { - mockDataViews.get = jest.fn().mockResolvedValue(mockDataView); + dataViewsService.get = jest.fn().mockResolvedValue(mockDataView); const controlEditor = render( @@ -97,13 +95,12 @@ describe('Data control editor', () => { controlId={controlId} controlType={controlType} initialDefaultPanelTitle={initialDefaultPanelTitle} - services={{ dataViews: mockDataViews }} /> ); await waitFor(() => { - expect(mockDataViews.get).toHaveBeenCalledTimes(1); + expect(dataViewsService.get).toHaveBeenCalledTimes(1); }); return controlEditor; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx index 5254fe200e97c..35e21ca3b407a 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx @@ -33,7 +33,6 @@ import { EuiToolTip, } from '@elastic/eui'; import { DataViewField } from '@kbn/data-views-plugin/common'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { LazyDataViewPicker, LazyFieldPicker, @@ -46,6 +45,7 @@ import { type ControlWidth, type DefaultDataControlState, } from '../../../../common'; +import { dataViewsService } from '../../../services/kibana_services'; import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; import type { ControlGroupApi } from '../../control_group/types'; import { DataControlEditorStrings } from './data_control_constants'; @@ -67,9 +67,6 @@ export interface ControlEditorProps< controlGroupApi: ControlGroupApi; // controls must always have a parent API onCancel: (newState: Partial) => void; onSave: (newState: Partial, type: string) => void; - services: { - dataViews: DataViewsPublicPluginStart; - }; } const FieldPicker = withSuspense(LazyFieldPicker, null); @@ -151,8 +148,6 @@ export const DataControlEditor = ) => { const [editorState, setEditorState] = useState>(initialState); const [defaultPanelTitle, setDefaultPanelTitle] = useState( @@ -163,16 +158,14 @@ export const DataControlEditor = (true); const editorConfig = useMemo(() => controlGroupApi.getEditorConfig(), [controlGroupApi]); - // TODO: Maybe remove `useAsync` - see https://github.com/elastic/kibana/pull/182842#discussion_r1624909709 const { loading: dataViewListLoading, value: dataViewListItems = [], error: dataViewListError, } = useAsync(async () => { - return dataViewService.getIdsWithTitle(); + return dataViewsService.getIdsWithTitle(); }); - // TODO: Maybe remove `useAsync` - see https://github.com/elastic/kibana/pull/182842#discussion_r1624909709 const { loading: dataViewLoading, value: { selectedDataView, fieldRegistry } = { @@ -185,7 +178,7 @@ export const DataControlEditor = { return await loadFieldRegistryFromDataView(dataView); @@ -21,7 +20,6 @@ export const getDataControlFieldRegistry = memoize( (dataView: DataView) => [dataView.id, JSON.stringify(dataView.fields.getAll())].join('|') ); -/** TODO: This function is duplicated from the controls plugin to avoid exporting it */ const loadFieldRegistryFromDataView = async ( dataView: DataView ): Promise => { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx index 8ff50a2bc4ada..d189d0aaa1ae9 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx @@ -7,10 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { coreMock } from '@kbn/core/public/mocks'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import type { DataView } from '@kbn/data-views-plugin/public'; import { first, skip } from 'rxjs'; +import { dataViewsService } from '../../../services/kibana_services'; import { ControlGroupApi } from '../../control_group/types'; import { initializeDataControl } from './initialize_data_control'; @@ -21,9 +20,8 @@ describe('initializeDataControl', () => { }; const editorStateManager = {}; const controlGroupApi = {} as unknown as ControlGroupApi; - const mockDataViews = dataViewPluginMocks.createStartContract(); - // @ts-ignore - mockDataViews.get = async (id: string): Promise => { + + dataViewsService.get = async (id: string): Promise => { if (id !== 'myDataViewId') { throw new Error(`Simulated error: no data view found for id ${id}`); } @@ -40,10 +38,6 @@ describe('initializeDataControl', () => { }, } as unknown as DataView; }; - const services = { - core: coreMock.createStart(), - dataViews: mockDataViews, - }; describe('dataViewId subscription', () => { describe('no blocking errors', () => { @@ -55,8 +49,7 @@ describe('initializeDataControl', () => { 'referenceNameSuffix', dataControlState, editorStateManager, - controlGroupApi, - services + controlGroupApi ); dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => { @@ -90,8 +83,7 @@ describe('initializeDataControl', () => { dataViewId: 'notGonnaFindMeDataViewId', }, editorStateManager, - controlGroupApi, - services + controlGroupApi ); dataControl.api.dataViews.pipe(skip(1), first()).subscribe(() => { @@ -129,8 +121,7 @@ describe('initializeDataControl', () => { fieldName: 'notGonnaFindMeFieldName', }, editorStateManager, - controlGroupApi, - services + controlGroupApi ); dataControl.api.defaultPanelTitle!.pipe(skip(1), first()).subscribe(() => { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts index 6b814efa1ec3d..11fb453d56350 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts @@ -10,19 +10,18 @@ import { isEqual } from 'lodash'; import { BehaviorSubject, combineLatest, debounceTime, first, skip, switchMap, tap } from 'rxjs'; -import { CoreStart } from '@kbn/core-lifecycle-browser'; import { DATA_VIEW_SAVED_OBJECT_TYPE, DataView, DataViewField, } from '@kbn/data-views-plugin/common'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { Filter } from '@kbn/es-query'; import { SerializedPanelState } from '@kbn/presentation-containers'; import { StateComparators } from '@kbn/presentation-publishing'; import { i18n } from '@kbn/i18n'; import type { DefaultControlState, DefaultDataControlState } from '../../../../common'; +import { dataViewsService } from '../../../services/kibana_services'; import type { ControlGroupApi } from '../../control_group/types'; import { initializeDefaultControlApi } from '../initialize_default_control_api'; import type { ControlApiInitialization, ControlStateManager } from '../types'; @@ -40,11 +39,7 @@ export const initializeDataControl = ( * responsible for managing */ editorStateManager: ControlStateManager, - controlGroupApi: ControlGroupApi, - services: { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; - } + controlGroupApi: ControlGroupApi ): { api: ControlApiInitialization; cleanup: () => void; @@ -88,7 +83,7 @@ export const initializeDataControl = ( switchMap(async (currentDataViewId) => { let dataView: DataView | undefined; try { - dataView = await services.dataViews.get(currentDataViewId); + dataView = await dataViewsService.get(currentDataViewId); return { dataView }; } catch (error) { return { error }; @@ -156,7 +151,6 @@ export const initializeDataControl = ( // open the editor to get the new state openDataControlEditor({ - services, onSave: ({ type: newType, state: newState }) => { if (newType === controlType) { // apply the changes from the new state via the state manager diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx index fe629555dea3c..08118702a003e 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx @@ -10,14 +10,14 @@ import React from 'react'; import deepEqual from 'react-fast-compare'; -import { CoreStart, OverlayRef } from '@kbn/core/public'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { OverlayRef } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { tracksOverlays } from '@kbn/presentation-containers'; import { apiHasParentApi } from '@kbn/presentation-publishing'; import { toMountPoint } from '@kbn/react-kibana-mount'; import type { DefaultDataControlState } from '../../../../common'; +import { coreServices } from '../../../services/kibana_services'; import type { ControlGroupApi } from '../../control_group/types'; import { DataControlEditor } from './data_control_editor'; @@ -30,7 +30,6 @@ export const openDataControlEditor = < initialDefaultPanelTitle, onSave, controlGroupApi, - services, }: { initialState: Partial; controlType?: string; @@ -38,10 +37,6 @@ export const openDataControlEditor = < initialDefaultPanelTitle?: string; onSave: ({ type, state }: { type: string; state: Partial }) => void; controlGroupApi: ControlGroupApi; - services: { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; - }; }): void => { const closeOverlay = (overlayRef: OverlayRef) => { if (apiHasParentApi(controlGroupApi) && tracksOverlays(controlGroupApi.parentApi)) { @@ -55,7 +50,7 @@ export const openDataControlEditor = < closeOverlay(overlay); return; } - services.core.overlays + coreServices.overlays .openConfirm( i18n.translate('controls.controlGroup.management.discard.sub', { defaultMessage: `Changes that you've made to this control will be discarded, are you sure you want to continue?`, @@ -80,7 +75,7 @@ export const openDataControlEditor = < }); }; - const overlay = services.core.overlays.openFlyout( + const overlay = coreServices.overlays.openFlyout( toMountPoint( controlGroupApi={controlGroupApi} @@ -95,11 +90,10 @@ export const openDataControlEditor = < closeOverlay(overlay); onSave({ type: selectedControlType, state }); }} - services={{ dataViews: services.dataViews }} />, { - theme: services.core.theme, - i18n: services.core.i18n, + theme: coreServices.theme, + i18n: coreServices.i18n, } ), { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx index c2b1d7d84250e..2e2cd341e8704 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx @@ -26,13 +26,11 @@ import { isValidSearch } from '../../../../../common/options_list/is_valid_searc import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlStateManager } from '../../types'; -import { DataControlServices } from '../types'; import { OptionsListFetchCache } from './options_list_fetch_cache'; import { OptionsListComponentApi, OptionsListComponentState, OptionsListControlApi } from './types'; export function fetchAndValidate$({ api, - services, stateManager, }: { api: Pick & @@ -41,7 +39,6 @@ export function fetchAndValidate$({ loadingSuggestions$: BehaviorSubject; debouncedSearchString: Observable; }; - services: DataControlServices; stateManager: ControlStateManager< Pick > & { @@ -126,7 +123,7 @@ export function fetchAndValidate$({ const newAbortController = new AbortController(); abortController = newAbortController; try { - return await requestCache.runFetchRequest(request, newAbortController.signal, services); + return await requestCache.runFetchRequest(request, newAbortController.signal); } catch (error) { return { error }; } diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx index 99e15a3d0f31f..20911d1cdb872 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx @@ -9,26 +9,22 @@ import React from 'react'; -import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { DataView } from '@kbn/data-views-plugin/common'; import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { act, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { coreServices, dataViewsService } from '../../../../services/kibana_services'; import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks'; import { getOptionsListControlFactory } from './get_options_list_control_factory'; describe('Options List Control Api', () => { const uuid = 'myControl1'; const controlGroupApi = getMockedControlGroupApi(); - const mockDataViews = dataViewPluginMocks.createStartContract(); - const mockCore = coreMock.createStart(); const waitOneTick = () => act(() => new Promise((resolve) => setTimeout(resolve, 0))); - mockDataViews.get = jest.fn().mockImplementation(async (id: string): Promise => { + dataViewsService.get = jest.fn().mockImplementation(async (id: string): Promise => { if (id !== 'myDataViewId') { throw new Error(`Simulated error: no data view found for id ${id}`); } @@ -60,11 +56,7 @@ describe('Options List Control Api', () => { return stubDataView; }); - const factory = getOptionsListControlFactory({ - core: mockCore, - data: dataPluginMock.createStartContract(), - dataViews: mockDataViews, - }); + const factory = getOptionsListControlFactory(); describe('filters$', () => { test('should not set filters$ when selectedOptions is not provided', async () => { @@ -177,7 +169,7 @@ describe('Options List Control Api', () => { describe('make selection', () => { beforeAll(() => { - mockCore.http.fetch = jest.fn().mockResolvedValue({ + coreServices.http.fetch = jest.fn().mockResolvedValue({ suggestions: [ { value: 'woof', docCount: 10 }, { value: 'bark', docCount: 15 }, diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx index d0c40736552ce..2a23ac9341ab9 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx @@ -9,7 +9,7 @@ import fastIsEqual from 'fast-deep-equal'; import React, { useEffect } from 'react'; -import { BehaviorSubject, combineLatest, debounceTime, filter, skip } from 'rxjs'; +import { BehaviorSubject, combineLatest, debounceTime, filter, map, skip } from 'rxjs'; import { buildExistsFilter, buildPhraseFilter, buildPhrasesFilter, Filter } from '@kbn/es-query'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; @@ -25,7 +25,7 @@ import type { } from '../../../../../common/options_list'; import { getSelectionAsFieldType, isValidSearch } from '../../../../../common/options_list'; import { initializeDataControl } from '../initialize_data_control'; -import type { DataControlFactory, DataControlServices } from '../types'; +import type { DataControlFactory } from '../types'; import { OptionsListControl } from './components/options_list_control'; import { OptionsListEditorOptions } from './components/options_list_editor_options'; import { @@ -39,9 +39,10 @@ import { initializeOptionsListSelections } from './options_list_control_selectio import { OptionsListStrings } from './options_list_strings'; import type { OptionsListControlApi } from './types'; -export const getOptionsListControlFactory = ( - services: DataControlServices -): DataControlFactory => { +export const getOptionsListControlFactory = (): DataControlFactory< + OptionsListControlState, + OptionsListControlApi +> => { return { type: OPTIONS_LIST_CONTROL, order: 3, // should always be first, since this is the most popular control @@ -78,6 +79,7 @@ export const getOptionsListControlFactory = ( const searchStringValid$ = new BehaviorSubject(true); const requestSize$ = new BehaviorSubject(MIN_OPTIONS_LIST_REQUEST_SIZE); + const dataLoading$ = new BehaviorSubject(undefined); const availableOptions$ = new BehaviorSubject(undefined); const invalidSelections$ = new BehaviorSubject>(new Set()); const totalCardinality$ = new BehaviorSubject(0); @@ -90,8 +92,7 @@ export const getOptionsListControlFactory = ( 'optionsListDataView', initialState, { searchTechnique: searchTechnique$, singleSelect: singleSelect$ }, - controlGroupApi, - services + controlGroupApi ); const selections = initializeOptionsListSelections( @@ -115,12 +116,16 @@ export const getOptionsListControlFactory = ( /** Handle loading state; since suggestion fetching and validation are tied, only need one loading subject */ const loadingSuggestions$ = new BehaviorSubject(false); - const dataLoadingSubscription = loadingSuggestions$ + const dataLoadingSubscription = combineLatest([ + loadingSuggestions$, + dataControl.api.dataLoading, + ]) .pipe( - debounceTime(100) // debounce set loading so that it doesn't flash as the user types + debounceTime(100), // debounce set loading so that it doesn't flash as the user types + map((values) => values.some((value) => value)) ) .subscribe((isLoading) => { - dataControl.api.setDataLoading(isLoading); + dataLoading$.next(isLoading); }); /** Debounce the search string changes to reduce the number of fetch requests */ @@ -161,7 +166,6 @@ export const getOptionsListControlFactory = ( /** Fetch the suggestions and perform validation */ const loadMoreSubject = new BehaviorSubject(null); const fetchSubscription = fetchAndValidate$({ - services, api: { ...dataControl.api, loadMoreSubject, @@ -235,6 +239,7 @@ export const getOptionsListControlFactory = ( const api = buildApi( { ...dataControl.api, + dataLoading: dataLoading$, getTypeDisplayName: OptionsListStrings.control.getDisplayName, serializeState: () => { const { rawState: dataControlState, references } = dataControl.serialize(); diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.ts index 548b1efebd02a..60b1463118733 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.ts @@ -20,7 +20,7 @@ import type { OptionsListResponse, OptionsListSuccessResponse, } from '../../../../../common/options_list/types'; -import type { DataControlServices } from '../types'; +import { coreServices, dataService } from '../../../../services/kibana_services'; const REQUEST_CACHE_SIZE = 50; // only store a max of 50 responses const REQUEST_CACHE_TTL = 1000 * 60; // time to live = 1 minute @@ -80,8 +80,7 @@ export class OptionsListFetchCache { public async runFetchRequest( request: OptionsListRequest, - abortSignal: AbortSignal, - services: DataControlServices + abortSignal: AbortSignal ): Promise { const requestHash = this.getRequestHash(request); @@ -90,11 +89,11 @@ export class OptionsListFetchCache { } else { const index = request.dataView.getIndexPattern(); - const timeService = services.data.query.timefilter.timefilter; + const timeService = dataService.query.timefilter.timefilter; const { query, filters, dataView, timeRange, field, ...passThroughProps } = request; const timeFilter = timeRange ? timeService.createFilter(dataView, timeRange) : undefined; const filtersToUse = [...(filters ?? []), ...(timeFilter ? [timeFilter] : [])]; - const config = getEsQueryConfig(services.core.uiSettings); + const config = getEsQueryConfig(coreServices.uiSettings); const esFilters = [buildEsQuery(dataView, query ?? [], filtersToUse ?? [], config)]; const requestBody = { @@ -105,7 +104,7 @@ export class OptionsListFetchCache { runtimeFieldMap: dataView.toSpec?.().runtimeFieldMap, }; - const result = await services.core.http.fetch( + const result = await coreServices.http.fetch( `/internal/controls/optionsList/${index}`, { version: '1', diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.ts index 417eb42d4b1bd..b58189a75daca 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.ts @@ -7,21 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { CoreSetup } from '@kbn/core/public'; -import type { ControlsPluginStartDeps } from '../../../../types'; -import { registerControlFactory } from '../../../control_factory_registry'; import { OPTIONS_LIST_CONTROL } from '../../../../../common'; +import { untilPluginStartServicesReady } from '../../../../services/kibana_services'; +import { registerControlFactory } from '../../../control_factory_registry'; -export function registerOptionsListControl(coreSetup: CoreSetup) { +export function registerOptionsListControl() { registerControlFactory(OPTIONS_LIST_CONTROL, async () => { - const [{ getOptionsListControlFactory }, [coreStart, depsStart]] = await Promise.all([ + const [{ getOptionsListControlFactory }] = await Promise.all([ import('./get_options_list_control_factory'), - coreSetup.getStartServices(), + untilPluginStartServicesReady(), ]); - return getOptionsListControlFactory({ - core: coreStart, - data: depsStart.data, - dataViews: depsStart.data.dataViews, - }); + return getOptionsListControlFactory(); }); } diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx index 76cb52981e8c1..925ec3443849a 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx @@ -11,13 +11,11 @@ import React from 'react'; import { of } from 'rxjs'; import { estypes } from '@elastic/elasticsearch'; -import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { DataViewField } from '@kbn/data-views-plugin/common'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { SerializedPanelState } from '@kbn/presentation-containers'; import { fireEvent, render, waitFor } from '@testing-library/react'; +import { dataService, dataViewsService } from '../../../../services/kibana_services'; import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks'; import { getRangesliderControlFactory } from './get_range_slider_control_factory'; import { RangesliderControlState } from './types'; @@ -31,11 +29,10 @@ describe('RangesliderControlApi', () => { const controlGroupApi = getMockedControlGroupApi(); - const dataStartServiceMock = dataPluginMock.createStartContract(); let totalResults = DEFAULT_TOTAL_RESULTS; let min: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MIN; let max: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MAX; - dataStartServiceMock.search.searchSource.create = jest.fn().mockImplementation(() => { + dataService.search.searchSource.create = jest.fn().mockImplementation(() => { let isAggsRequest = false; return { setField: (key: string) => { @@ -54,9 +51,8 @@ describe('RangesliderControlApi', () => { }, }; }); - const mockDataViews = dataViewPluginMocks.createStartContract(); - mockDataViews.get = jest.fn().mockImplementation(async (id: string): Promise => { + dataViewsService.get = jest.fn().mockImplementation(async (id: string): Promise => { if (id !== 'myDataViewId') { throw new Error(`no data view found for id ${id}`); } @@ -82,11 +78,7 @@ describe('RangesliderControlApi', () => { } as unknown as DataView; }); - const factory = getRangesliderControlFactory({ - core: coreMock.createStart(), - data: dataStartServiceMock, - dataViews: mockDataViews, - }); + const factory = getRangesliderControlFactory(); beforeEach(() => { totalResults = DEFAULT_TOTAL_RESULTS; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.tsx index 596206dc2f4f6..3ad3b97af7414 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.tsx @@ -16,7 +16,7 @@ import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { RANGE_SLIDER_CONTROL } from '../../../../../common'; import { initializeDataControl } from '../initialize_data_control'; -import type { DataControlFactory, DataControlServices } from '../types'; +import type { DataControlFactory } from '../types'; import { RangeSliderControl } from './components/range_slider_control'; import { hasNoResults$ } from './has_no_results'; import { minMax$ } from './min_max'; @@ -24,9 +24,10 @@ import { initializeRangeControlSelections } from './range_control_selections'; import { RangeSliderStrings } from './range_slider_strings'; import type { RangesliderControlApi, RangesliderControlState } from './types'; -export const getRangesliderControlFactory = ( - services: DataControlServices -): DataControlFactory => { +export const getRangesliderControlFactory = (): DataControlFactory< + RangesliderControlState, + RangesliderControlApi +> => { return { type: RANGE_SLIDER_CONTROL, getIconType: () => 'controlsHorizontal', @@ -71,8 +72,7 @@ export const getRangesliderControlFactory = ( { step: step$, }, - controlGroupApi, - services + controlGroupApi ); const selections = initializeRangeControlSelections( @@ -111,13 +111,14 @@ export const getRangesliderControlFactory = ( } ); - const dataLoadingSubscription = combineLatest([loadingMinMax$, loadingHasNoResults$]) + const dataLoadingSubscription = combineLatest([ + loadingMinMax$, + loadingHasNoResults$, + dataControl.api.dataLoading, + ]) .pipe( - map((values) => { - return values.some((value) => { - return value; - }); - }) + debounceTime(100), + map((values) => values.some((value) => value)) ) .subscribe((isLoading) => { dataLoading$.next(isLoading); @@ -138,7 +139,6 @@ export const getRangesliderControlFactory = ( const min$ = new BehaviorSubject(undefined); const minMaxSubscription = minMax$({ controlFetch$, - data: services.data, dataViews$: dataControl.api.dataViews, fieldName$: dataControl.stateManager.fieldName, setIsLoading: (isLoading: boolean) => { @@ -198,7 +198,6 @@ export const getRangesliderControlFactory = ( const selectionHasNoResults$ = new BehaviorSubject(false); const hasNotResultsSubscription = hasNoResults$({ controlFetch$, - data: services.data, dataViews$: dataControl.api.dataViews, rangeFilters$: dataControl.api.filters$, ignoreParentSettings$: controlGroupApi.ignoreParentSettings$, diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts index 27676f5f7b649..24d4510b3fc22 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts @@ -8,25 +8,23 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { PublishesDataViews } from '@kbn/presentation-publishing'; -import { combineLatest, lastValueFrom, Observable, switchMap, tap } from 'rxjs'; +import { Observable, combineLatest, lastValueFrom, switchMap, tap } from 'rxjs'; +import { dataService } from '../../../../services/kibana_services'; import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlGroupApi } from '../../../control_group/types'; import { DataControlApi } from '../types'; export function hasNoResults$({ controlFetch$, - data, dataViews$, rangeFilters$, ignoreParentSettings$, setIsLoading, }: { controlFetch$: Observable; - data: DataPublicPluginStart; dataViews$?: PublishesDataViews['dataViews']; rangeFilters$: DataControlApi['filters$']; ignoreParentSettings$: ControlGroupApi['ignoreParentSettings$']; @@ -53,7 +51,6 @@ export function hasNoResults$({ prevRequestAbortController = abortController; return await hasNoResults({ abortSignal: abortController.signal, - data, dataView, rangeFilter, ...controlFetchContext, @@ -71,7 +68,6 @@ export function hasNoResults$({ async function hasNoResults({ abortSignal, - data, dataView, filters, query, @@ -79,14 +75,13 @@ async function hasNoResults({ timeRange, }: { abortSignal: AbortSignal; - data: DataPublicPluginStart; dataView: DataView; filters?: Filter[]; query?: Query | AggregateQuery; rangeFilter: Filter; timeRange?: TimeRange; }): Promise { - const searchSource = await data.search.searchSource.create(); + const searchSource = await dataService.search.searchSource.create(); searchSource.setField('size', 0); searchSource.setField('index', dataView); // Tracking total hits accurately has a performance cost @@ -97,7 +92,7 @@ async function hasNoResults({ const allFilters = filters ? [...filters] : []; allFilters.push(rangeFilter); if (timeRange) { - const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); + const timeFilter = dataService.query.timefilter.timefilter.createFilter(dataView, timeRange); if (timeFilter) allFilters.push(timeFilter); } if (allFilters.length) { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts index d3335e182f101..8e4d5e00374af 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts @@ -8,26 +8,24 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing'; -import { combineLatest, lastValueFrom, Observable, of, startWith, switchMap, tap } from 'rxjs'; import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload'; +import { Observable, combineLatest, lastValueFrom, of, startWith, switchMap, tap } from 'rxjs'; +import { dataService } from '../../../../services/kibana_services'; import { ControlFetchContext } from '../../../control_group/control_fetch'; import { ControlGroupApi } from '../../../control_group/types'; export function minMax$({ controlFetch$, controlGroupApi, - data, dataViews$, fieldName$, setIsLoading, }: { controlFetch$: Observable; controlGroupApi: ControlGroupApi; - data: DataPublicPluginStart; dataViews$: PublishesDataViews['dataViews']; fieldName$: PublishingSubject; setIsLoading: (isLoading: boolean) => void; @@ -60,7 +58,6 @@ export function minMax$({ prevRequestAbortController = abortController; return await getMinMax({ abortSignal: abortController.signal, - data, dataView, field: dataViewField, ...controlFetchContext, @@ -77,7 +74,6 @@ export function minMax$({ export async function getMinMax({ abortSignal, - data, dataView, field, filters, @@ -85,20 +81,19 @@ export async function getMinMax({ timeRange, }: { abortSignal: AbortSignal; - data: DataPublicPluginStart; dataView: DataView; field: DataViewField; filters?: Filter[]; query?: Query | AggregateQuery; timeRange?: TimeRange; }): Promise<{ min: number | undefined; max: number | undefined }> { - const searchSource = await data.search.searchSource.create(); + const searchSource = await dataService.search.searchSource.create(); searchSource.setField('size', 0); searchSource.setField('index', dataView); const allFilters = filters ? [...filters] : []; if (timeRange) { - const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, timeRange); + const timeFilter = dataService.query.timefilter.timefilter.createFilter(dataView, timeRange); if (timeFilter) allFilters.push(timeFilter); } if (allFilters.length) { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.ts index 4f77fc3bac7e4..0e1c0fd925792 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.ts @@ -7,22 +7,17 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { CoreSetup } from '@kbn/core/public'; -import type { ControlsPluginStartDeps } from '../../../../types'; -import { registerControlFactory } from '../../../control_factory_registry'; import { RANGE_SLIDER_CONTROL } from '../../../../../common'; +import { untilPluginStartServicesReady } from '../../../../services/kibana_services'; +import { registerControlFactory } from '../../../control_factory_registry'; -export function registerRangeSliderControl(coreSetup: CoreSetup) { +export function registerRangeSliderControl() { registerControlFactory(RANGE_SLIDER_CONTROL, async () => { - const [{ getRangesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ + const [{ getRangesliderControlFactory }] = await Promise.all([ import('./get_range_slider_control_factory'), - coreSetup.getStartServices(), + untilPluginStartServicesReady(), ]); - return getRangesliderControlFactory({ - core: coreStart, - data: depsStart.data, - dataViews: depsStart.data.dataViews, - }); + return getRangesliderControlFactory(); }); } diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/types.ts b/src/plugins/controls/public/react_controls/controls/data_controls/types.ts index 9eac141642402..89912e6eabb03 100644 --- a/src/plugins/controls/public/react_controls/controls/data_controls/types.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/types.ts @@ -7,10 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { CoreStart } from '@kbn/core/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewField } from '@kbn/data-views-plugin/common'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { FieldFormatConvertFunction } from '@kbn/field-formats-plugin/common'; import { HasEditCapabilities, @@ -62,12 +59,6 @@ export const isDataControlFactory = ( return typeof (factory as DataControlFactory).isFieldCompatible === 'function'; }; -export interface DataControlServices { - core: CoreStart; - data: DataPublicPluginStart; - dataViews: DataViewsPublicPluginStart; -} - interface DataControlField { field: DataViewField; compatibleControlTypes: string[]; diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/get_time_range_meta.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_time_range_meta.ts index 1cccb264d19e7..5c84cfbdef508 100644 --- a/src/plugins/controls/public/react_controls/controls/timeslider_control/get_time_range_meta.ts +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_time_range_meta.ts @@ -9,6 +9,7 @@ import { EuiRangeTick } from '@elastic/eui'; import { TimeRange } from '@kbn/es-query'; +import { coreServices, dataService } from '../../../services/kibana_services'; import { FROM_INDEX, getStepSize, @@ -17,7 +18,6 @@ import { roundUpToNextStepSizeFactor, TO_INDEX, } from './time_utils'; -import { Services } from './types'; export interface TimeRangeMeta { format: string; @@ -29,12 +29,9 @@ export interface TimeRangeMeta { timeRangeMin: number; } -export function getTimeRangeMeta( - timeRange: TimeRange | undefined, - services: Services -): TimeRangeMeta { - const nextBounds = timeRangeToBounds(timeRange ?? getDefaultTimeRange(services), services); - const ticks = getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], getTimezone(services)); +export function getTimeRangeMeta(timeRange: TimeRange | undefined): TimeRangeMeta { + const nextBounds = timeRangeToBounds(timeRange ?? getDefaultTimeRange()); + const ticks = getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], getTimezone()); const { format, stepSize } = getStepSize(ticks); return { format, @@ -47,17 +44,17 @@ export function getTimeRangeMeta( }; } -export function getTimezone(services: Services) { - return services.core.uiSettings.get('dateFormat:tz', 'Browser'); +export function getTimezone() { + return coreServices.uiSettings.get('dateFormat:tz', 'Browser'); } -function getDefaultTimeRange(services: Services) { - const defaultTimeRange = services.core.uiSettings.get('timepicker:timeDefaults'); +function getDefaultTimeRange() { + const defaultTimeRange = coreServices.uiSettings.get('timepicker:timeDefaults'); return defaultTimeRange ? defaultTimeRange : { from: 'now-15m', to: 'now' }; } -function timeRangeToBounds(timeRange: TimeRange, services: Services): [number, number] { - const timeRangeBounds = services.data.query.timefilter.timefilter.calculateBounds(timeRange); +function timeRangeToBounds(timeRange: TimeRange): [number, number] { + const timeRangeBounds = dataService.query.timefilter.timefilter.calculateBounds(timeRange); return timeRangeBounds.min === undefined || timeRangeBounds.max === undefined ? [Date.now() - 1000 * 60 * 15, Date.now()] : [timeRangeBounds.min.valueOf(), timeRangeBounds.max.valueOf()]; diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx index 12381ad83c407..d4b8ff6c13461 100644 --- a/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx @@ -7,14 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; + import dateMath from '@kbn/datemath'; import { TimeRange } from '@kbn/es-query'; import { StateComparators } from '@kbn/presentation-publishing'; import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { BehaviorSubject } from 'rxjs'; + +import { dataService } from '../../../services/kibana_services'; import { getMockedControlGroupApi } from '../mocks/control_mocks'; import { ControlApiRegistration } from '../types'; import { getTimesliderControlFactory } from './get_timeslider_control_factory'; @@ -28,18 +29,14 @@ describe('TimesliderControlApi', () => { }; const controlGroupApi = getMockedControlGroupApi(dashboardApi); - const dataStartServiceMock = dataPluginMock.createStartContract(); - dataStartServiceMock.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => { + dataService.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => { const now = new Date(); return { min: dateMath.parse(timeRange.from, { forceNow: now }), max: dateMath.parse(timeRange.to, { roundUp: true, forceNow: now }), }; }; - const factory = getTimesliderControlFactory({ - core: coreMock.createStart(), - data: dataStartServiceMock, - }); + const factory = getTimesliderControlFactory(); let comparators: StateComparators | undefined; function buildApiMock( api: ControlApiRegistration, diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx index 20baf8fb545e1..cfc8e50bee1b5 100644 --- a/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx @@ -36,22 +36,23 @@ import { roundDownToNextStepSizeFactor, roundUpToNextStepSizeFactor, } from './time_utils'; -import { Services, Timeslice, TimesliderControlApi, TimesliderControlState } from './types'; +import { Timeslice, TimesliderControlApi, TimesliderControlState } from './types'; const displayName = i18n.translate('controls.timesliderControl.displayName', { defaultMessage: 'Time slider', }); -export const getTimesliderControlFactory = ( - services: Services -): ControlFactory => { +export const getTimesliderControlFactory = (): ControlFactory< + TimesliderControlState, + TimesliderControlApi +> => { return { type: TIME_SLIDER_CONTROL, getIconType: () => 'search', getDisplayName: () => displayName, buildControl: async (initialState, buildApi, uuid, controlGroupApi) => { const { timeRangeMeta$, formatDate, cleanupTimeRangeSubscription } = - initTimeRangeSubscription(controlGroupApi, services); + initTimeRangeSubscription(controlGroupApi); const timeslice$ = new BehaviorSubject<[number, number] | undefined>(undefined); const isAnchored$ = new BehaviorSubject(initialState.isAnchored); const isPopoverOpen$ = new BehaviorSubject(false); diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_subscription.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_subscription.ts index 7b4a2deb9f0d1..7934e9deaa9b4 100644 --- a/src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_subscription.ts +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_subscription.ts @@ -14,26 +14,23 @@ import moment from 'moment'; import { BehaviorSubject, skip } from 'rxjs'; import { getTimeRangeMeta, getTimezone, TimeRangeMeta } from './get_time_range_meta'; import { getMomentTimezone } from './time_utils'; -import { Services } from './types'; -export function initTimeRangeSubscription(controlGroupApi: unknown, services: Services) { +export function initTimeRangeSubscription(controlGroupApi: unknown) { const timeRange$ = apiHasParentApi(controlGroupApi) && apiPublishesTimeRange(controlGroupApi.parentApi) ? controlGroupApi.parentApi.timeRange$ : new BehaviorSubject(undefined); - const timeRangeMeta$ = new BehaviorSubject( - getTimeRangeMeta(timeRange$.value, services) - ); + const timeRangeMeta$ = new BehaviorSubject(getTimeRangeMeta(timeRange$.value)); const timeRangeSubscription = timeRange$.pipe(skip(1)).subscribe((timeRange) => { - timeRangeMeta$.next(getTimeRangeMeta(timeRange, services)); + timeRangeMeta$.next(getTimeRangeMeta(timeRange)); }); return { timeRangeMeta$, formatDate: (epoch: number) => { return moment - .tz(epoch, getMomentTimezone(getTimezone(services))) + .tz(epoch, getMomentTimezone(getTimezone())) .locale(i18n.getLocale()) .format(timeRangeMeta$.value.format); }, diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.ts index 8fbf23305820f..338a52631c931 100644 --- a/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.ts +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.ts @@ -7,20 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { CoreSetup } from '@kbn/core/public'; -import type { ControlsPluginStartDeps } from '../../../types'; -import { registerControlFactory } from '../../control_factory_registry'; import { TIME_SLIDER_CONTROL } from '../../../../common'; +import { untilPluginStartServicesReady } from '../../../services/kibana_services'; +import { registerControlFactory } from '../../control_factory_registry'; -export function registerTimeSliderControl(coreSetup: CoreSetup) { +export function registerTimeSliderControl() { registerControlFactory(TIME_SLIDER_CONTROL, async () => { - const [{ getTimesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ + const [{ getTimesliderControlFactory }] = await Promise.all([ import('./get_timeslider_control_factory'), - coreSetup.getStartServices(), + untilPluginStartServicesReady(), ]); - return getTimesliderControlFactory({ - core: coreStart, - data: depsStart.data, - }); + return getTimesliderControlFactory(); }); } diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/types.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/types.ts index 702d02ae9accc..634e0351e77eb 100644 --- a/src/plugins/controls/public/react_controls/controls/timeslider_control/types.ts +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/types.ts @@ -7,8 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { CoreStart } from '@kbn/core/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { PublishesPanelTitle, PublishesTimeslice } from '@kbn/presentation-publishing'; import type { DefaultControlState } from '../../../../common'; import type { DefaultControlApi } from '../types'; @@ -25,8 +23,3 @@ export interface TimesliderControlState extends DefaultControlState { export type TimesliderControlApi = DefaultControlApi & Pick & PublishesTimeslice; - -export interface Services { - core: CoreStart; - data: DataPublicPluginStart; -} diff --git a/src/plugins/controls/public/react_controls/controls/types.ts b/src/plugins/controls/public/react_controls/controls/types.ts index 85045f8bd70ba..ce4ad9f194fa3 100644 --- a/src/plugins/controls/public/react_controls/controls/types.ts +++ b/src/plugins/controls/public/react_controls/controls/types.ts @@ -40,15 +40,15 @@ export type DefaultControlApi = PublishesDataLoading & HasType & HasUniqueId & HasParentApi & { - // Can not use HasSerializableState interface - // HasSerializableState types serializeState as function returning 'MaybePromise' - // Controls serializeState is sync - serializeState: () => SerializedPanelState; - /** TODO: Make these non-public as part of https://github.com/elastic/kibana/issues/174961 */ setDataLoading: (loading: boolean) => void; setBlockingError: (error: Error | undefined) => void; grow: PublishingSubject; width: PublishingSubject; + + // Can not use HasSerializableState interface + // HasSerializableState types serializeState as function returning 'MaybePromise' + // Controls serializeState is sync + serializeState: () => SerializedPanelState; }; export type ControlApiRegistration = Omit< @@ -62,7 +62,6 @@ export type ControlApiInitialization; -// TODO: Move this to the Control plugin's setup contract export interface ControlFactory< State extends DefaultControlState = DefaultControlState, ControlApi extends DefaultControlApi = DefaultControlApi diff --git a/src/plugins/controls/public/react_controls/external_api/control_group_renderer.test.tsx b/src/plugins/controls/public/react_controls/external_api/control_group_renderer.test.tsx index 58269308f1846..e034ca817908e 100644 --- a/src/plugins/controls/public/react_controls/external_api/control_group_renderer.test.tsx +++ b/src/plugins/controls/public/react_controls/external_api/control_group_renderer.test.tsx @@ -9,26 +9,22 @@ import React from 'react'; -import { coreMock } from '@kbn/core/public/mocks'; -import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { Filter } from '@kbn/es-query'; import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing'; import { act, render, waitFor } from '@testing-library/react'; import { ControlGroupRendererApi } from '.'; +import { CONTROL_GROUP_TYPE } from '../..'; import { getControlGroupEmbeddableFactory } from '../control_group/get_control_group_factory'; import { ControlGroupRenderer, ControlGroupRendererProps } from './control_group_renderer'; -import { CONTROL_GROUP_TYPE } from '../..'; type ParentApiType = PublishesUnifiedSearch & { unifiedSearchFilters$?: PublishingSubject; }; describe('control group renderer', () => { - const core = coreMock.createStart(); - const dataViews = dataViewPluginMocks.createStartContract(); - const factory = getControlGroupEmbeddableFactory({ core, dataViews }); + const factory = getControlGroupEmbeddableFactory(); const buildControlGroupSpy = jest.spyOn(factory, 'buildEmbeddable'); const mountControlGroupRenderer = async ( diff --git a/src/plugins/controls/public/services/controls/controls.stub.ts b/src/plugins/controls/public/services/controls/controls.stub.ts deleted file mode 100644 index 2e182998c9071..0000000000000 --- a/src/plugins/controls/public/services/controls/controls.stub.ts +++ /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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlFactory, DefaultControlApi } from '../../react_controls/controls/types'; -import { ControlsServiceType } from './types'; - -export type ControlsServiceFactory = PluginServiceFactory; -export const controlsServiceFactory = () => getStubControlsService(); - -export const getStubControlsService = () => { - const controlsFactoriesMap: { [key: string]: ControlFactory } = {}; - - const mockRegisterControlFactory = async < - State extends object = object, - ApiType extends DefaultControlApi = DefaultControlApi - >( - controlType: string, - getFactory: () => Promise> - ) => { - controlsFactoriesMap[controlType] = (await getFactory()) as ControlFactory; - }; - - const mockGetControlFactory = < - State extends object = object, - ApiType extends DefaultControlApi = DefaultControlApi - >( - type: string - ) => { - return controlsFactoriesMap[type] as ControlFactory; - }; - - const getAllControlTypes = () => Object.keys(controlsFactoriesMap); - - return { - registerControlFactory: mockRegisterControlFactory, - getControlFactory: mockGetControlFactory, - getAllControlTypes, - }; -}; diff --git a/src/plugins/controls/public/services/controls/controls_service.ts b/src/plugins/controls/public/services/controls/controls_service.ts deleted file mode 100644 index c794c056a4f8d..0000000000000 --- a/src/plugins/controls/public/services/controls/controls_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - getAllControlTypes, - getControlFactory, - registerControlFactory, -} from '../../react_controls/control_factory_registry'; -import { ControlsServiceType } from './types'; - -export const controlsServiceFactory = () => controlsService; - -// export controls service directly for use in plugin setup lifecycle -export const controlsService: ControlsServiceType = { - registerControlFactory, - getControlFactory, - getAllControlTypes, -}; diff --git a/src/plugins/controls/public/services/controls/types.ts b/src/plugins/controls/public/services/controls/types.ts deleted file mode 100644 index d9011819d815c..0000000000000 --- a/src/plugins/controls/public/services/controls/types.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { - getAllControlTypes, - getControlFactory, - registerControlFactory, -} from '../../react_controls/control_factory_registry'; - -export type ControlsServiceFactory = PluginServiceFactory; - -export interface ControlsServiceType { - registerControlFactory: typeof registerControlFactory; - getControlFactory: typeof getControlFactory; - getAllControlTypes: typeof getAllControlTypes; -} diff --git a/src/plugins/controls/public/services/core/core.stub.ts b/src/plugins/controls/public/services/core/core.stub.ts deleted file mode 100644 index 052ddf46129e2..0000000000000 --- a/src/plugins/controls/public/services/core/core.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { analyticsServiceMock, coreMock, themeServiceMock } from '@kbn/core/public/mocks'; -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsCoreService } from './types'; - -export type CoreServiceFactory = PluginServiceFactory; - -export const coreServiceFactory: CoreServiceFactory = () => { - const corePluginMock = coreMock.createStart(); - return { - analytics: analyticsServiceMock.createAnalyticsServiceStart(), - theme: themeServiceMock.createSetupContract(), - i18n: corePluginMock.i18n, - notifications: corePluginMock.notifications, - }; -}; diff --git a/src/plugins/controls/public/services/core/core_service.ts b/src/plugins/controls/public/services/core/core_service.ts deleted file mode 100644 index 090784c32d806..0000000000000 --- a/src/plugins/controls/public/services/core/core_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsCoreService } from './types'; -import { ControlsPluginStartDeps } from '../../types'; - -export type CoreServiceFactory = KibanaPluginServiceFactory< - ControlsCoreService, - ControlsPluginStartDeps ->; - -export const coreServiceFactory: CoreServiceFactory = ({ coreStart }) => { - const { analytics, theme, i18n, notifications } = coreStart; - - return { - analytics, - theme, - i18n, - notifications, - }; -}; diff --git a/src/plugins/controls/public/services/core/types.ts b/src/plugins/controls/public/services/core/types.ts deleted file mode 100644 index 9424a490b3f3d..0000000000000 --- a/src/plugins/controls/public/services/core/types.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { CoreStart } from '@kbn/core/public'; - -export interface ControlsCoreService { - analytics: CoreStart['analytics']; - i18n: CoreStart['i18n']; - theme: CoreStart['theme']; - notifications: CoreStart['notifications']; -} diff --git a/src/plugins/controls/public/services/data/data.stub.ts b/src/plugins/controls/public/services/data/data.stub.ts deleted file mode 100644 index 5cd50a68768ef..0000000000000 --- a/src/plugins/controls/public/services/data/data.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { of } from 'rxjs'; -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { ControlsDataService } from './types'; - -export type DataServiceFactory = PluginServiceFactory; -export const dataServiceFactory: DataServiceFactory = () => ({ - query: {} as unknown as DataPublicPluginStart['query'], - searchSource: { - create: () => ({ - setField: () => {}, - fetch$: () => - of({ - rawResponse: { aggregations: { minAgg: { value: 0 }, maxAgg: { value: 1000 } } }, - }), - }), - } as unknown as DataPublicPluginStart['search']['searchSource'], - timefilter: { - createFilter: () => {}, - } as unknown as DataPublicPluginStart['query']['timefilter']['timefilter'], - fetchFieldRange: () => Promise.resolve({ min: 0, max: 100 }), -}); diff --git a/src/plugins/controls/public/services/data/data_service.ts b/src/plugins/controls/public/services/data/data_service.ts deleted file mode 100644 index 84a36c2775dcd..0000000000000 --- a/src/plugins/controls/public/services/data/data_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsPluginStartDeps } from '../../types'; -import { ControlsDataService } from './types'; - -export type DataServiceFactory = KibanaPluginServiceFactory< - ControlsDataService, - ControlsPluginStartDeps ->; - -export const dataServiceFactory: DataServiceFactory = ({ startPlugins }) => { - const { - data: { query: queryPlugin, search }, - } = startPlugins; - - return { - query: queryPlugin, - searchSource: search.searchSource, - timefilter: queryPlugin.timefilter.timefilter, - }; -}; diff --git a/src/plugins/controls/public/services/data/types.ts b/src/plugins/controls/public/services/data/types.ts deleted file mode 100644 index 68a7553ee08dc..0000000000000 --- a/src/plugins/controls/public/services/data/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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -export interface ControlsDataService { - query: DataPublicPluginStart['query']; - searchSource: DataPublicPluginStart['search']['searchSource']; - timefilter: DataPublicPluginStart['query']['timefilter']['timefilter']; -} diff --git a/src/plugins/controls/public/services/data_views/data_views.stub.ts b/src/plugins/controls/public/services/data_views/data_views.stub.ts deleted file mode 100644 index f2ffcf5f08a9d..0000000000000 --- a/src/plugins/controls/public/services/data_views/data_views.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { ControlsDataViewsService } from './types'; - -export type DataViewsServiceFactory = PluginServiceFactory; - -let currentDataView: DataView | undefined; -export const injectStorybookDataView = (dataView?: DataView) => (currentDataView = dataView); - -export const dataViewsServiceFactory: DataViewsServiceFactory = () => ({ - get: ((dataViewId) => - new Promise((resolve, reject) => - setTimeout(() => { - if (!currentDataView) { - reject( - new Error( - 'mock DataViews service currentDataView is undefined, call injectStorybookDataView to set' - ) - ); - } else if (currentDataView.id === dataViewId) { - resolve(currentDataView); - } else { - reject( - new Error( - `mock DataViews service currentDataView.id: ${currentDataView.id} does not match requested dataViewId: ${dataViewId}` - ) - ); - } - }, 100) - ) as unknown) as DataViewsPublicPluginStart['get'], - getIdsWithTitle: (() => - new Promise((resolve) => - setTimeout(() => { - const idsWithTitle: Array<{ id: string | undefined; title: string }> = []; - if (currentDataView) { - idsWithTitle.push({ id: currentDataView.id, title: currentDataView.title }); - } - resolve(idsWithTitle); - }, 100) - ) as unknown) as DataViewsPublicPluginStart['getIdsWithTitle'], - getDefaultId: () => Promise.resolve(currentDataView?.id ?? null), -}); diff --git a/src/plugins/controls/public/services/data_views/data_views_service.ts b/src/plugins/controls/public/services/data_views/data_views_service.ts deleted file mode 100644 index 4ad4c0b8d2241..0000000000000 --- a/src/plugins/controls/public/services/data_views/data_views_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsPluginStartDeps } from '../../types'; -import { ControlsDataViewsService } from './types'; - -export type DataViewsServiceFactory = KibanaPluginServiceFactory< - ControlsDataViewsService, - ControlsPluginStartDeps ->; - -export const dataViewsServiceFactory: DataViewsServiceFactory = ({ startPlugins }) => { - const { - dataViews: { get, getIdsWithTitle, getDefaultId }, - } = startPlugins; - - return { - get, - getDefaultId, - getIdsWithTitle, - }; -}; diff --git a/src/plugins/controls/public/services/data_views/types.ts b/src/plugins/controls/public/services/data_views/types.ts deleted file mode 100644 index a204af439634e..0000000000000 --- a/src/plugins/controls/public/services/data_views/types.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; - -export interface ControlsDataViewsService { - get: DataViewsPublicPluginStart['get']; - getDefaultId: DataViewsPublicPluginStart['getDefaultId']; - getIdsWithTitle: DataViewsPublicPluginStart['getIdsWithTitle']; -} diff --git a/src/plugins/controls/public/services/embeddable/embeddable.stub.ts b/src/plugins/controls/public/services/embeddable/embeddable.stub.ts deleted file mode 100644 index 5f75b4e7b2d14..0000000000000 --- a/src/plugins/controls/public/services/embeddable/embeddable.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { ControlsEmbeddableService } from './types'; - -export type EmbeddableServiceFactory = PluginServiceFactory; -export const embeddableServiceFactory: EmbeddableServiceFactory = () => { - const { doStart } = embeddablePluginMock.createInstance(); - const start = doStart(); - return { getEmbeddableFactory: start.getEmbeddableFactory }; -}; diff --git a/src/plugins/controls/public/services/embeddable/embeddable_service.ts b/src/plugins/controls/public/services/embeddable/embeddable_service.ts deleted file mode 100644 index 79c556f69c058..0000000000000 --- a/src/plugins/controls/public/services/embeddable/embeddable_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsEmbeddableService } from './types'; -import { ControlsPluginStartDeps } from '../../types'; - -export type EmbeddableServiceFactory = KibanaPluginServiceFactory< - ControlsEmbeddableService, - ControlsPluginStartDeps ->; - -export const embeddableServiceFactory: EmbeddableServiceFactory = ({ startPlugins }) => { - return { - getEmbeddableFactory: startPlugins.embeddable.getEmbeddableFactory, - }; -}; diff --git a/src/plugins/controls/public/services/http/http.stub.ts b/src/plugins/controls/public/services/http/http.stub.ts deleted file mode 100644 index ead893559b488..0000000000000 --- a/src/plugins/controls/public/services/http/http.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { HttpResponse } from '@kbn/core/public'; -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsHTTPService } from './types'; - -type HttpServiceFactory = PluginServiceFactory; - -export const httpServiceFactory: HttpServiceFactory = () => ({ - get: async () => ({} as unknown as HttpResponse), - fetch: async () => ({} as unknown as HttpResponse), -}); diff --git a/src/plugins/controls/public/services/http/http_service.ts b/src/plugins/controls/public/services/http/http_service.ts deleted file mode 100644 index fac813ea92272..0000000000000 --- a/src/plugins/controls/public/services/http/http_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsHTTPService } from './types'; -import { ControlsPluginStartDeps } from '../../types'; - -export type HttpServiceFactory = KibanaPluginServiceFactory< - ControlsHTTPService, - ControlsPluginStartDeps ->; -export const httpServiceFactory: HttpServiceFactory = ({ coreStart }) => { - const { - http: { get, fetch }, - } = coreStart; - - return { - get, - fetch, - }; -}; diff --git a/src/plugins/controls/public/services/http/types.ts b/src/plugins/controls/public/services/http/types.ts deleted file mode 100644 index 0072bc0dacff0..0000000000000 --- a/src/plugins/controls/public/services/http/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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { CoreSetup } from '@kbn/core/public'; - -export interface ControlsHTTPService { - get: CoreSetup['http']['get']; - fetch: CoreSetup['http']['fetch']; -} diff --git a/src/plugins/controls/public/services/kibana_services.ts b/src/plugins/controls/public/services/kibana_services.ts new file mode 100644 index 0000000000000..b9e5fadcecaa0 --- /dev/null +++ b/src/plugins/controls/public/services/kibana_services.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { BehaviorSubject } from 'rxjs'; + +import { CoreStart } from '@kbn/core/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +import { ControlsPluginStartDeps } from '../types'; + +export let coreServices: CoreStart; +export let dataService: DataPublicPluginStart; +export let dataViewsService: DataViewsPublicPluginStart; + +const servicesReady$ = new BehaviorSubject(false); + +export const setKibanaServices = (kibanaCore: CoreStart, deps: ControlsPluginStartDeps) => { + coreServices = kibanaCore; + dataService = deps.data; + dataViewsService = deps.dataViews; + + servicesReady$.next(true); +}; + +export const untilPluginStartServicesReady = () => { + if (servicesReady$.value) return Promise.resolve(); + return new Promise((resolve) => { + const subscription = servicesReady$.subscribe((isInitialized) => { + if (isInitialized) { + subscription.unsubscribe(); + resolve(); + } + }); + }); +}; diff --git a/src/plugins/controls/public/services/mocks.ts b/src/plugins/controls/public/services/mocks.ts new file mode 100644 index 0000000000000..231323c37a12a --- /dev/null +++ b/src/plugins/controls/public/services/mocks.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; + +import { setKibanaServices } from './kibana_services'; + +export const setStubKibanaServices = () => { + setKibanaServices(coreMock.createStart(), { + data: dataPluginMock.createStartContract(), + dataViews: dataViewPluginMocks.createStartContract(), + uiActions: uiActionsPluginMock.createStartContract(), + }); +}; diff --git a/src/plugins/controls/public/services/overlays/overlays.stub.ts b/src/plugins/controls/public/services/overlays/overlays.stub.ts deleted file mode 100644 index d7938f5a8a3ce..0000000000000 --- a/src/plugins/controls/public/services/overlays/overlays.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - MountPoint, - OverlayFlyoutOpenOptions, - OverlayModalConfirmOptions, - OverlayRef, -} from '@kbn/core/public'; -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsOverlaysService } from './types'; - -type OverlaysServiceFactory = PluginServiceFactory; - -class StubRef implements OverlayRef { - public readonly onClose: Promise = Promise.resolve(); - - public close(): Promise { - return this.onClose; - } -} - -export const overlaysServiceFactory: OverlaysServiceFactory = () => ({ - openFlyout: (mount: MountPoint, options?: OverlayFlyoutOpenOptions) => new StubRef(), - openConfirm: (message: MountPoint | string, options?: OverlayModalConfirmOptions) => - Promise.resolve(true), -}); diff --git a/src/plugins/controls/public/services/overlays/overlays_service.ts b/src/plugins/controls/public/services/overlays/overlays_service.ts deleted file mode 100644 index b8e7309e4eb31..0000000000000 --- a/src/plugins/controls/public/services/overlays/overlays_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsPluginStartDeps } from '../../types'; -import { ControlsOverlaysService } from './types'; - -export type OverlaysServiceFactory = KibanaPluginServiceFactory< - ControlsOverlaysService, - ControlsPluginStartDeps ->; -export const overlaysServiceFactory: OverlaysServiceFactory = ({ coreStart }) => { - const { - overlays: { openFlyout, openConfirm }, - } = coreStart; - - return { - openFlyout, - openConfirm, - }; -}; diff --git a/src/plugins/controls/public/services/overlays/types.ts b/src/plugins/controls/public/services/overlays/types.ts deleted file mode 100644 index 7cb5fd2975549..0000000000000 --- a/src/plugins/controls/public/services/overlays/types.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - MountPoint, - OverlayFlyoutOpenOptions, - OverlayModalConfirmOptions, - OverlayRef, -} from '@kbn/core/public'; - -export interface ControlsOverlaysService { - openFlyout(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef; - openConfirm(message: MountPoint | string, options?: OverlayModalConfirmOptions): Promise; -} diff --git a/src/plugins/controls/public/services/plugin_services.stub.ts b/src/plugins/controls/public/services/plugin_services.stub.ts deleted file mode 100644 index 1e7b3982f88cc..0000000000000 --- a/src/plugins/controls/public/services/plugin_services.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - PluginServiceProvider, - PluginServiceProviders, - PluginServiceRegistry, - PluginServices, -} from '@kbn/presentation-util-plugin/public'; - -import { ControlsPluginStart } from '../types'; -import { ControlsServices } from './types'; - -import { controlsServiceFactory } from './controls/controls.stub'; -import { coreServiceFactory } from './core/core.stub'; -import { dataServiceFactory } from './data/data.stub'; -import { dataViewsServiceFactory } from './data_views/data_views.stub'; -import { embeddableServiceFactory } from './embeddable/embeddable.stub'; -import { httpServiceFactory } from './http/http.stub'; -import { overlaysServiceFactory } from './overlays/overlays.stub'; -import { settingsServiceFactory } from './settings/settings.stub'; -import { unifiedSearchServiceFactory } from './unified_search/unified_search.stub'; -import { storageServiceFactory } from './storage/storage_service.stub'; - -export const providers: PluginServiceProviders = { - embeddable: new PluginServiceProvider(embeddableServiceFactory), - controls: new PluginServiceProvider(controlsServiceFactory), - data: new PluginServiceProvider(dataServiceFactory), - dataViews: new PluginServiceProvider(dataViewsServiceFactory), - http: new PluginServiceProvider(httpServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), - settings: new PluginServiceProvider(settingsServiceFactory), - core: new PluginServiceProvider(coreServiceFactory), - storage: new PluginServiceProvider(storageServiceFactory), - unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory), -}; - -export const pluginServices = new PluginServices(); - -export const registry = new PluginServiceRegistry(providers); - -export const getStubPluginServices = (): ControlsPluginStart => { - pluginServices.setRegistry(registry.start({})); - return { - getControlFactory: pluginServices.getServices().controls.getControlFactory, - getAllControlTypes: pluginServices.getServices().controls.getAllControlTypes, - }; -}; diff --git a/src/plugins/controls/public/services/plugin_services.ts b/src/plugins/controls/public/services/plugin_services.ts deleted file mode 100644 index d0d2552871173..0000000000000 --- a/src/plugins/controls/public/services/plugin_services.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { - KibanaPluginServiceParams, - PluginServiceProvider, - PluginServiceProviders, - PluginServiceRegistry, - PluginServices, -} from '@kbn/presentation-util-plugin/public'; -import { ControlsPluginStartDeps } from '../types'; -import { ControlsServices } from './types'; - -import { controlsServiceFactory } from './controls/controls_service'; -import { coreServiceFactory } from './core/core_service'; -import { dataServiceFactory } from './data/data_service'; -import { dataViewsServiceFactory } from './data_views/data_views_service'; -import { embeddableServiceFactory } from './embeddable/embeddable_service'; -import { httpServiceFactory } from './http/http_service'; -import { overlaysServiceFactory } from './overlays/overlays_service'; -import { settingsServiceFactory } from './settings/settings_service'; -import { controlsStorageServiceFactory } from './storage/storage_service'; -import { unifiedSearchServiceFactory } from './unified_search/unified_search_service'; - -export const providers: PluginServiceProviders< - ControlsServices, - KibanaPluginServiceParams -> = { - controls: new PluginServiceProvider(controlsServiceFactory), - data: new PluginServiceProvider(dataServiceFactory), - dataViews: new PluginServiceProvider(dataViewsServiceFactory), - embeddable: new PluginServiceProvider(embeddableServiceFactory), - http: new PluginServiceProvider(httpServiceFactory), - overlays: new PluginServiceProvider(overlaysServiceFactory), - settings: new PluginServiceProvider(settingsServiceFactory), - storage: new PluginServiceProvider(controlsStorageServiceFactory), - core: new PluginServiceProvider(coreServiceFactory), - unifiedSearch: new PluginServiceProvider(unifiedSearchServiceFactory), -}; - -export const pluginServices = new PluginServices(); - -export const registry = new PluginServiceRegistry< - ControlsServices, - KibanaPluginServiceParams ->(providers); diff --git a/src/plugins/controls/public/services/settings/settings.stub.ts b/src/plugins/controls/public/services/settings/settings.stub.ts deleted file mode 100644 index 6ee5bdce4f2f4..0000000000000 --- a/src/plugins/controls/public/services/settings/settings.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsSettingsService } from './types'; - -export type SettingsServiceFactory = PluginServiceFactory; -export const settingsServiceFactory: SettingsServiceFactory = () => ({ - getTimezone: () => 'Browser', - getDateFormat: () => 'MMM D, YYYY @ HH:mm:ss.SSS', - getDefaultTimeRange: () => ({ from: 'now-15m', to: 'now' }), -}); diff --git a/src/plugins/controls/public/services/settings/settings_service.ts b/src/plugins/controls/public/services/settings/settings_service.ts deleted file mode 100644 index e5f8751503a67..0000000000000 --- a/src/plugins/controls/public/services/settings/settings_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsSettingsService } from './types'; -import { ControlsPluginStartDeps } from '../../types'; - -export type SettingsServiceFactory = KibanaPluginServiceFactory< - ControlsSettingsService, - ControlsPluginStartDeps ->; - -export const settingsServiceFactory: SettingsServiceFactory = ({ coreStart }) => { - return { - getDateFormat: () => { - return coreStart.uiSettings.get('dateFormat', 'MMM D, YYYY @ HH:mm:ss.SSS'); - }, - getTimezone: () => { - return coreStart.uiSettings.get('dateFormat:tz', 'Browser'); - }, - getDefaultTimeRange: () => { - const defaultTimeRange = coreStart.uiSettings.get('timepicker:timeDefaults'); - return defaultTimeRange ? defaultTimeRange : { from: 'now-15m', to: 'now' }; - }, - }; -}; diff --git a/src/plugins/controls/public/services/settings/types.ts b/src/plugins/controls/public/services/settings/types.ts deleted file mode 100644 index f8b29adeb85a7..0000000000000 --- a/src/plugins/controls/public/services/settings/types.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { TimeRange } from '@kbn/es-query'; - -export interface ControlsSettingsService { - getTimezone: () => string; - getDateFormat: () => string; - getDefaultTimeRange: () => TimeRange; -} diff --git a/src/plugins/controls/public/services/storage/storage_service.stub.ts b/src/plugins/controls/public/services/storage/storage_service.stub.ts deleted file mode 100644 index 389d804b51bb0..0000000000000 --- a/src/plugins/controls/public/services/storage/storage_service.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsStorageService } from './types'; - -type StorageServiceFactory = PluginServiceFactory; - -export const storageServiceFactory: StorageServiceFactory = () => { - return { - getShowInvalidSelectionWarning: () => false, - setShowInvalidSelectionWarning: (value: boolean) => null, - }; -}; diff --git a/src/plugins/controls/public/services/storage/storage_service.ts b/src/plugins/controls/public/services/storage/storage_service.ts deleted file mode 100644 index 9f0403c505e74..0000000000000 --- a/src/plugins/controls/public/services/storage/storage_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { ControlsStorageService } from './types'; - -const STORAGE_KEY = 'controls:showInvalidSelectionWarning'; - -class StorageService implements ControlsStorageService { - private storage: Storage; - - constructor() { - this.storage = new Storage(localStorage); - } - - getShowInvalidSelectionWarning = () => { - return this.storage.get(STORAGE_KEY); - }; - - setShowInvalidSelectionWarning = (value: boolean) => { - this.storage.set(STORAGE_KEY, value); - }; -} - -export const controlsStorageServiceFactory = () => new StorageService(); diff --git a/src/plugins/controls/public/services/types.ts b/src/plugins/controls/public/services/types.ts deleted file mode 100644 index c38ad6b64fac6..0000000000000 --- a/src/plugins/controls/public/services/types.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { ControlsServiceType } from './controls/types'; -import { ControlsCoreService } from './core/types'; -import { ControlsDataService } from './data/types'; -import { ControlsDataViewsService } from './data_views/types'; -import { ControlsEmbeddableService } from './embeddable/types'; -import { ControlsHTTPService } from './http/types'; -import { ControlsOverlaysService } from './overlays/types'; -import { ControlsSettingsService } from './settings/types'; -import { ControlsStorageService } from './storage/types'; -import { ControlsUnifiedSearchService } from './unified_search/types'; - -export interface ControlsServices { - // dependency services - dataViews: ControlsDataViewsService; - overlays: ControlsOverlaysService; - embeddable: ControlsEmbeddableService; - data: ControlsDataService; - unifiedSearch: ControlsUnifiedSearchService; - http: ControlsHTTPService; - settings: ControlsSettingsService; - core: ControlsCoreService; - - // controls plugin's own services - controls: ControlsServiceType; - storage: ControlsStorageService; -} diff --git a/src/plugins/controls/public/services/unified_search/types.ts b/src/plugins/controls/public/services/unified_search/types.ts deleted file mode 100644 index 28aa8e05feb2d..0000000000000 --- a/src/plugins/controls/public/services/unified_search/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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; - -export interface ControlsUnifiedSearchService { - autocomplete: UnifiedSearchPublicPluginStart['autocomplete']; -} diff --git a/src/plugins/controls/public/services/unified_search/unified_search.stub.ts b/src/plugins/controls/public/services/unified_search/unified_search.stub.ts deleted file mode 100644 index c7b1959c8bbc0..0000000000000 --- a/src/plugins/controls/public/services/unified_search/unified_search.stub.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { DataViewField } from '@kbn/data-views-plugin/common'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; - -import { ControlsUnifiedSearchService } from './types'; - -let valueSuggestionMethod = ({ field, query }: { field: DataViewField; query: string }) => - Promise.resolve(['storybook', 'default', 'values']); -export const replaceValueSuggestionMethod = ( - newMethod: ({ field, query }: { field: DataViewField; query: string }) => Promise -) => (valueSuggestionMethod = newMethod); - -export type UnifiedSearchServiceFactory = PluginServiceFactory; -export const unifiedSearchServiceFactory: UnifiedSearchServiceFactory = () => ({ - autocomplete: { - getValueSuggestions: valueSuggestionMethod, - } as unknown as UnifiedSearchPublicPluginStart['autocomplete'], -}); diff --git a/src/plugins/controls/public/services/unified_search/unified_search_service.ts b/src/plugins/controls/public/services/unified_search/unified_search_service.ts deleted file mode 100644 index 45d10cfa9ac7d..0000000000000 --- a/src/plugins/controls/public/services/unified_search/unified_search_service.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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; -import { ControlsPluginStartDeps } from '../../types'; -import { ControlsUnifiedSearchService } from './types'; - -export type UnifiedSearchServiceFactory = KibanaPluginServiceFactory< - ControlsUnifiedSearchService, - ControlsPluginStartDeps ->; - -export const unifiedSearchServiceFactory: UnifiedSearchServiceFactory = ({ startPlugins }) => { - const { - unifiedSearch: { autocomplete }, - } = startPlugins; - - return { - autocomplete, - }; -}; diff --git a/src/plugins/controls/public/types.ts b/src/plugins/controls/public/types.ts index 2ecbd38763603..bed3260bb4401 100644 --- a/src/plugins/controls/public/types.ts +++ b/src/plugins/controls/public/types.ts @@ -9,11 +9,8 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; - -import { ControlsServiceType } from './services/controls/types'; export interface CanClearSelections { clearSelections: () => void; @@ -26,22 +23,11 @@ export const isClearableControl = (control: unknown): control is CanClearSelecti /** * Plugin types */ -export interface ControlsPluginSetup { - registerControlFactory: ControlsServiceType['registerControlFactory']; -} - -export interface ControlsPluginStart { - getControlFactory: ControlsServiceType['getControlFactory']; - getAllControlTypes: ControlsServiceType['getAllControlTypes']; -} - export interface ControlsPluginSetupDeps { embeddable: EmbeddableSetup; } export interface ControlsPluginStartDeps { uiActions: UiActionsStart; - embeddable: EmbeddableStart; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; } diff --git a/src/plugins/controls/server/control_group/control_group_persistence.ts b/src/plugins/controls/server/control_group/control_group_persistence.ts index eb63b28ccecba..e90aa850c6d1a 100644 --- a/src/plugins/controls/server/control_group/control_group_persistence.ts +++ b/src/plugins/controls/server/control_group/control_group_persistence.ts @@ -10,7 +10,7 @@ import { SerializableRecord } from '@kbn/utility-types'; import { - DEFAULT_CONTROL_STYLE, + DEFAULT_CONTROL_LABEL_POSITION, type ControlGroupRuntimeState, type ControlGroupSerializedState, type ControlPanelState, @@ -19,7 +19,7 @@ import { export const getDefaultControlGroupState = (): SerializableControlGroupState => ({ panels: {}, - labelPosition: DEFAULT_CONTROL_STYLE, + labelPosition: DEFAULT_CONTROL_LABEL_POSITION, chainingSystem: 'HIERARCHICAL', autoApplySelections: true, ignoreParentSettings: { diff --git a/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts b/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts index f51b9a5b5b62c..04b0aaa3e6f78 100644 --- a/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts +++ b/src/plugins/controls/server/options_list/options_list_cluster_settings_route.ts @@ -15,7 +15,7 @@ export const setupOptionsListClusterSettingsRoute = ({ http }: CoreSetup) => { router.versioned .get({ access: 'internal', - path: '/internal/controls/optionsList/getExpensiveQueriesSetting', + path: '/internal/controls/getExpensiveQueriesSetting', }) .addVersion( { diff --git a/src/plugins/controls/tsconfig.json b/src/plugins/controls/tsconfig.json index fc9d6572ccf2f..abcafa291358e 100644 --- a/src/plugins/controls/tsconfig.json +++ b/src/plugins/controls/tsconfig.json @@ -35,7 +35,6 @@ "@kbn/presentation-containers", "@kbn/presentation-publishing", "@kbn/content-management-utils", - "@kbn/core-lifecycle-browser", "@kbn/field-formats-plugin", "@kbn/presentation-panel-plugin", "@kbn/shared-ux-utility" diff --git a/src/plugins/unified_search/public/query_string_input/esql_menu_popover.test.tsx b/src/plugins/unified_search/public/query_string_input/esql_menu_popover.test.tsx index 5b81edac7c178..9a263aa79510d 100644 --- a/src/plugins/unified_search/public/query_string_input/esql_menu_popover.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/esql_menu_popover.test.tsx @@ -36,7 +36,7 @@ describe('ESQLMenuPopover', () => { renderESQLPopover(); expect(screen.getByTestId('esql-menu-button')).toBeInTheDocument(); await userEvent.click(screen.getByRole('button')); - + expect(screen.getByTestId('esql-quick-reference')).toBeInTheDocument(); expect(screen.getByTestId('esql-examples')).toBeInTheDocument(); expect(screen.getByTestId('esql-about')).toBeInTheDocument(); expect(screen.getByTestId('esql-feedback')).toBeInTheDocument(); diff --git a/src/plugins/unified_search/public/query_string_input/esql_menu_popover.tsx b/src/plugins/unified_search/public/query_string_input/esql_menu_popover.tsx index 003827344c307..d684448670c42 100644 --- a/src/plugins/unified_search/public/query_string_input/esql_menu_popover.tsx +++ b/src/plugins/unified_search/public/query_string_input/esql_menu_popover.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useCallback } from 'react'; import { EuiPopover, EuiButton, @@ -19,6 +19,7 @@ import { import { useKibana } from '@kbn/kibana-react-plugin/public'; import { i18n } from '@kbn/i18n'; import { FEEDBACK_LINK } from '@kbn/esql-utils'; +import { LanguageDocumentationFlyout } from '@kbn/language-documentation-popover'; import type { IUnifiedSearchPluginServices } from '../types'; export const ESQLMenuPopover = () => { @@ -26,15 +27,33 @@ export const ESQLMenuPopover = () => { const { docLinks } = kibana.services; const [isESQLMenuPopoverOpen, setIsESQLMenuPopoverOpen] = useState(false); + const [isLanguageComponentOpen, setIsLanguageComponentOpen] = useState(false); + + const toggleLanguageComponent = useCallback(async () => { + setIsLanguageComponentOpen(!isLanguageComponentOpen); + setIsESQLMenuPopoverOpen(false); + }, [isLanguageComponentOpen]); + const esqlPanelItems = useMemo(() => { const panelItems: EuiContextMenuPanelProps['items'] = []; panelItems.push( + toggleLanguageComponent()} + > + {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.quickReference', { + defaultMessage: 'Quick Reference', + })} + , setIsESQLMenuPopoverOpen(false)} > {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.documentation', { defaultMessage: 'Documentation', @@ -46,6 +65,7 @@ export const ESQLMenuPopover = () => { data-test-subj="esql-examples" target="_blank" href={docLinks.links.query.queryESQLExamples} + onClick={() => setIsESQLMenuPopoverOpen(false)} > {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.documentation', { defaultMessage: 'Example queries', @@ -58,6 +78,7 @@ export const ESQLMenuPopover = () => { data-test-subj="esql-feedback" target="_blank" href={FEEDBACK_LINK} + onClick={() => setIsESQLMenuPopoverOpen(false)} > {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.documentation', { defaultMessage: 'Submit feedback', @@ -65,32 +86,44 @@ export const ESQLMenuPopover = () => { ); return panelItems; - }, [docLinks.links.query.queryESQL, docLinks.links.query.queryESQLExamples]); + }, [ + docLinks.links.query.queryESQL, + docLinks.links.query.queryESQLExamples, + toggleLanguageComponent, + ]); return ( - setIsESQLMenuPopoverOpen(!isESQLMenuPopoverOpen)} - data-test-subj="esql-menu-button" - size="s" - > - {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.label', { - defaultMessage: 'ES|QL help', - })} - - } - panelProps={{ - ['data-test-subj']: 'esql-menu-popover', - css: { width: 240 }, - }} - isOpen={isESQLMenuPopoverOpen} - closePopover={() => setIsESQLMenuPopoverOpen(false)} - panelPaddingSize="s" - display="block" - > - - + <> + setIsESQLMenuPopoverOpen(!isESQLMenuPopoverOpen)} + data-test-subj="esql-menu-button" + size="s" + > + {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.label', { + defaultMessage: 'ES|QL help', + })} + + } + panelProps={{ + ['data-test-subj']: 'esql-menu-popover', + css: { width: 240 }, + }} + isOpen={isESQLMenuPopoverOpen} + closePopover={() => setIsESQLMenuPopoverOpen(false)} + panelPaddingSize="s" + display="block" + > + + + + ); }; diff --git a/src/plugins/unified_search/tsconfig.json b/src/plugins/unified_search/tsconfig.json index 909c0031b5a31..e836f3c6daa67 100644 --- a/src/plugins/unified_search/tsconfig.json +++ b/src/plugins/unified_search/tsconfig.json @@ -46,7 +46,8 @@ "@kbn/data-view-utils", "@kbn/esql-utils", "@kbn/react-kibana-mount", - "@kbn/field-utils" + "@kbn/field-utils", + "@kbn/language-documentation-popover" ], "exclude": [ "target/**/*", diff --git a/test/functional/apps/console/_misc_console_behavior.ts b/test/functional/apps/console/_misc_console_behavior.ts index 68bcc6e7558cf..05cd34d1c2a7f 100644 --- a/test/functional/apps/console/_misc_console_behavior.ts +++ b/test/functional/apps/console/_misc_console_behavior.ts @@ -18,7 +18,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'console', 'header']); - describe('misc console behavior', function testMiscConsoleBehavior() { + // Failing: See https://github.com/elastic/kibana/issues/193309 + describe.skip('misc console behavior', function testMiscConsoleBehavior() { this.tags('includeFirefox'); before(async () => { await browser.setWindowSize(1200, 800); diff --git a/x-pack/plugins/cases/server/telemetry/collect_telemetry_data.ts b/x-pack/plugins/cases/server/telemetry/collect_telemetry_data.ts index cabb7743a540d..1bcf599f014fb 100644 --- a/x-pack/plugins/cases/server/telemetry/collect_telemetry_data.ts +++ b/x-pack/plugins/cases/server/telemetry/collect_telemetry_data.ts @@ -11,7 +11,7 @@ import { getCasesSystemActionData } from './queries/case_system_action'; import { getUserCommentsTelemetryData } from './queries/comments'; import { getConfigurationTelemetryData } from './queries/configuration'; import { getConnectorsTelemetryData } from './queries/connectors'; -import { getPushedTelemetryData } from './queries/pushes'; +import { getPushedTelemetryData } from './queries/push'; import { getUserActionsTelemetryData } from './queries/user_actions'; import type { CasesTelemetry, CollectTelemetryDataParams } from './types'; diff --git a/x-pack/plugins/cases/server/telemetry/index.ts b/x-pack/plugins/cases/server/telemetry/index.ts index 5f10dcc6a3c72..c30d34d6c215c 100644 --- a/x-pack/plugins/cases/server/telemetry/index.ts +++ b/x-pack/plugins/cases/server/telemetry/index.ts @@ -5,12 +5,7 @@ * 2.0. */ -import type { - CoreSetup, - ISavedObjectsRepository, - Logger, - PluginInitializerContext, -} from '@kbn/core/server'; +import type { CoreSetup, Logger, PluginInitializerContext } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; @@ -25,6 +20,7 @@ import { } from '../../common/constants'; import type { CasesTelemetry } from './types'; import { casesSchema } from './schema'; +import { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client'; export { scheduleCasesTelemetryTask } from './schedule_telemetry_task'; @@ -42,13 +38,18 @@ export const createCasesTelemetry = ({ usageCollection, logger, }: CreateCasesTelemetryArgs) => { - const getInternalSavedObjectClient = async (): Promise => { + const getInternalSavedObjectClient = async (): Promise => { const [coreStart] = await core.getStartServices(); - return coreStart.savedObjects.createInternalRepository([ + const soClient = coreStart.savedObjects.createInternalRepository([ ...SAVED_OBJECT_TYPES, FILE_SO_TYPE, CASE_RULES_SAVED_OBJECT, ]); + + // Wrapping the internalRepository with the `TelemetrySavedObjectsClient` + // to ensure some best practices when collecting "all the telemetry" + // (i.e.: `.find` requests should query all spaces) + return new TelemetrySavedObjectsClient(soClient); }; taskManager.registerTaskDefinitions({ diff --git a/x-pack/plugins/cases/server/telemetry/queries/alerts.test.ts b/x-pack/plugins/cases/server/telemetry/queries/alerts.test.ts index 0eaa99c57c0f3..11636b50ebd4e 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/alerts.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/alerts.test.ts @@ -7,12 +7,15 @@ import { loggingSystemMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { getAlertsTelemetryData } from './alerts'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('alerts', () => { const logger = loggingSystemMock.createLogger(); describe('getAlertsTelemetryData', () => { const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + savedObjectsClient.find.mockResolvedValue({ total: 5, saved_objects: [], @@ -35,7 +38,10 @@ describe('alerts', () => { }); it('it returns the correct res', async () => { - const res = await getAlertsTelemetryData({ savedObjectsClient, logger }); + const res = await getAlertsTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { total: 5, @@ -48,7 +54,7 @@ describe('alerts', () => { }); it('should call find with correct arguments', async () => { - await getAlertsTelemetryData({ savedObjectsClient, logger }); + await getAlertsTelemetryData({ savedObjectsClient: telemetrySavedObjectsClient, logger }); expect(savedObjectsClient.find).toBeCalledWith({ aggs: { counts: { @@ -117,6 +123,7 @@ describe('alerts', () => { page: 0, perPage: 0, type: 'cases-comments', + namespaces: ['*'], }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/case_system_action.test.ts b/x-pack/plugins/cases/server/telemetry/queries/case_system_action.test.ts index 6009d646431ed..0f121639e0f32 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/case_system_action.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/case_system_action.test.ts @@ -7,12 +7,14 @@ import { loggingSystemMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { getCasesSystemActionData } from './case_system_action'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('casesSystemAction', () => { const logger = loggingSystemMock.createLogger(); describe('getCasesSystemActionData', () => { const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); beforeEach(() => { jest.clearAllMocks(); @@ -26,7 +28,10 @@ describe('casesSystemAction', () => { }); it('calculates the metrics correctly', async () => { - const res = await getCasesSystemActionData({ savedObjectsClient, logger }); + const res = await getCasesSystemActionData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ totalCasesCreated: 4, totalRules: 2 }); }); @@ -38,8 +43,49 @@ describe('casesSystemAction', () => { page: 1, }); - const res = await getCasesSystemActionData({ savedObjectsClient, logger }); + const res = await getCasesSystemActionData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); + expect(res).toEqual({ totalCasesCreated: 0, totalRules: 0 }); }); + + it('should call find with correct arguments', async () => { + savedObjectsClient.find.mockResolvedValue({ + total: 1, + saved_objects: [], + per_page: 1, + page: 1, + }); + + await getCasesSystemActionData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); + + expect(savedObjectsClient.find.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "counterSum": Object { + "sum": Object { + "field": "cases-rules.attributes.counter", + }, + }, + "totalRules": Object { + "cardinality": Object { + "field": "cases-rules.attributes.rules.id", + }, + }, + }, + "namespaces": Array [ + "*", + ], + "page": 1, + "perPage": 1, + "type": "cases-rules", + } + `); + }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/case_system_action.ts b/x-pack/plugins/cases/server/telemetry/queries/case_system_action.ts index 0e05006e3c437..6eda6b477611c 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/case_system_action.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/case_system_action.ts @@ -26,6 +26,7 @@ export const getCasesSystemActionData = async ({ cardinality: { field: `${CASE_RULES_SAVED_OBJECT}.attributes.rules.id` }, }, }, + namespaces: ['*'], }); return { diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts index 560997e8802be..fdfe39f940e9b 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.test.ts @@ -15,6 +15,7 @@ import type { FileAttachmentAggregationResults, } from '../types'; import { getCasesTelemetryData } from './cases'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; const MOCK_FIND_TOTAL = 5; const SOLUTION_TOTAL = 1; @@ -23,6 +24,7 @@ describe('getCasesTelemetryData', () => { describe('getCasesTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); const mockFind = (aggs: object, so: SavedObjectsFindResponse['saved_objects'] = []) => { savedObjectsClient.find.mockResolvedValueOnce({ @@ -322,7 +324,10 @@ describe('getCasesTelemetryData', () => { }; }; - const res = await getCasesTelemetryData({ savedObjectsClient, logger }); + const res = await getCasesTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); const allAttachmentsTotal = 5; const allAttachmentsAverage = allAttachmentsTotal / MOCK_FIND_TOTAL; @@ -406,7 +411,7 @@ describe('getCasesTelemetryData', () => { it('should call find with correct arguments', async () => { mockResponse(); - await getCasesTelemetryData({ savedObjectsClient, logger }); + await getCasesTelemetryData({ savedObjectsClient: telemetrySavedObjectsClient, logger }); expect(savedObjectsClient.find.mock.calls[0][0]).toMatchInlineSnapshot(` Object { @@ -660,6 +665,9 @@ describe('getCasesTelemetryData', () => { }, }, }, + "namespaces": Array [ + "*", + ], "page": 0, "perPage": 0, "type": "cases", @@ -974,6 +982,9 @@ describe('getCasesTelemetryData', () => { }, }, }, + "namespaces": Array [ + "*", + ], "page": 0, "perPage": 0, "type": "cases-comments", @@ -1023,6 +1034,7 @@ describe('getCasesTelemetryData', () => { page: 0, perPage: 0, type: 'cases-comments', + namespaces: ['*'], }); expect(savedObjectsClient.find.mock.calls[3][0]).toEqual({ @@ -1068,6 +1080,7 @@ describe('getCasesTelemetryData', () => { page: 0, perPage: 0, type: 'cases-user-actions', + namespaces: ['*'], }); for (const [index, sortField] of ['created_at', 'updated_at', 'closed_at'].entries()) { @@ -1079,6 +1092,7 @@ describe('getCasesTelemetryData', () => { sortField, sortOrder: 'desc', type: 'cases', + namespaces: ['*'], }); } @@ -1172,6 +1186,9 @@ describe('getCasesTelemetryData', () => { "function": "is", "type": "function", }, + "namespaces": Array [ + "*", + ], "page": 0, "perPage": 0, "type": "file", diff --git a/x-pack/plugins/cases/server/telemetry/queries/cases.ts b/x-pack/plugins/cases/server/telemetry/queries/cases.ts index abd1979d752e8..81eefd6af1d1d 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/cases.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/cases.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ISavedObjectsRepository, SavedObjectsFindResponse } from '@kbn/core/server'; +import type { SavedObjectsFindResponse } from '@kbn/core/server'; import { FILE_SO_TYPE } from '@kbn/files-plugin/common'; import { fromKueryExpression } from '@kbn/es-query'; import { @@ -37,6 +37,7 @@ import { } from './utils'; import type { CasePersistedAttributes } from '../../common/types/case'; import { CasePersistedStatus } from '../../common/types/case'; +import type { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; export const getLatestCasesDates = async ({ savedObjectsClient, @@ -48,6 +49,7 @@ export const getLatestCasesDates = async ({ sortField, sortOrder: 'desc', type: CASE_SAVED_OBJECT, + namespaces: ['*'], }); const savedObjects = await Promise.all([ @@ -145,7 +147,7 @@ export const getCasesTelemetryData = async ({ }; const getCasesSavedObjectTelemetry = async ( - savedObjectsClient: ISavedObjectsRepository + savedObjectsClient: TelemetrySavedObjectsClient ): Promise> => { const caseByOwnerAggregationQuery = OWNERS.reduce( (aggQuery, owner) => ({ @@ -169,6 +171,7 @@ const getCasesSavedObjectTelemetry = async ( page: 0, perPage: 0, type: CASE_SAVED_OBJECT, + namespaces: ['*'], aggs: { ...caseByOwnerAggregationQuery, ...getCountsAggregationQuery(CASE_SAVED_OBJECT), @@ -231,7 +234,7 @@ const getAssigneesAggregations = () => ({ }); const getCommentsSavedObjectTelemetry = async ( - savedObjectsClient: ISavedObjectsRepository + savedObjectsClient: TelemetrySavedObjectsClient ): Promise> => { const attachmentRegistries = () => ({ externalReferenceTypes: { @@ -275,6 +278,7 @@ const getCommentsSavedObjectTelemetry = async ( page: 0, perPage: 0, type: CASE_COMMENT_SAVED_OBJECT, + namespaces: ['*'], aggs: { ...attachmentsByOwnerAggregationQuery, ...attachmentRegistries(), @@ -288,7 +292,7 @@ const getCommentsSavedObjectTelemetry = async ( }; const getFilesTelemetry = async ( - savedObjectsClient: ISavedObjectsRepository + savedObjectsClient: TelemetrySavedObjectsClient ): Promise> => { const averageSize = () => ({ averageSize: { @@ -332,17 +336,19 @@ const getFilesTelemetry = async ( perPage: 0, type: FILE_SO_TYPE, filter: filterCaseIdExists, + namespaces: ['*'], aggs: { ...filesByOwnerAggregationQuery, ...averageSize(), ...top20MimeTypes() }, }); }; const getAlertsTelemetry = async ( - savedObjectsClient: ISavedObjectsRepository + savedObjectsClient: TelemetrySavedObjectsClient ): Promise> => { return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_COMMENT_SAVED_OBJECT, + namespaces: ['*'], filter: getOnlyAlertsCommentsFilter(), aggs: { ...getReferencesAggregationQuery({ @@ -355,12 +361,13 @@ const getAlertsTelemetry = async ( }; const getConnectorsTelemetry = async ( - savedObjectsClient: ISavedObjectsRepository + savedObjectsClient: TelemetrySavedObjectsClient ): Promise> => { return savedObjectsClient.find({ page: 0, perPage: 0, type: CASE_USER_ACTION_SAVED_OBJECT, + namespaces: ['*'], filter: getOnlyConnectorsFilter(), aggs: { ...getReferencesAggregationQuery({ diff --git a/x-pack/plugins/cases/server/telemetry/queries/comments.test.ts b/x-pack/plugins/cases/server/telemetry/queries/comments.test.ts index 9eed9b4040992..d3104bd9a79ad 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/comments.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/comments.test.ts @@ -7,11 +7,14 @@ import { loggingSystemMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { getUserCommentsTelemetryData } from './comments'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('comments', () => { describe('getUserCommentsTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + savedObjectsClient.find.mockResolvedValue({ total: 5, saved_objects: [], @@ -34,7 +37,10 @@ describe('comments', () => { }); it('it returns the correct res', async () => { - const res = await getUserCommentsTelemetryData({ savedObjectsClient, logger }); + const res = await getUserCommentsTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { total: 5, @@ -47,7 +53,10 @@ describe('comments', () => { }); it('should call find with correct arguments', async () => { - await getUserCommentsTelemetryData({ savedObjectsClient, logger }); + await getUserCommentsTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(savedObjectsClient.find).toBeCalledWith({ aggs: { counts: { @@ -116,6 +125,7 @@ describe('comments', () => { page: 0, perPage: 0, type: 'cases-comments', + namespaces: ['*'], }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/configuration.test.ts b/x-pack/plugins/cases/server/telemetry/queries/configuration.test.ts index 57c7c067a13cf..7e69c60980db1 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/configuration.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/configuration.test.ts @@ -8,11 +8,14 @@ import { loggingSystemMock, savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { CustomFieldTypes } from '../../../common/types/domain'; import { getConfigurationTelemetryData } from './configuration'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('configuration', () => { describe('getConfigurationTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + savedObjectsClient.find.mockResolvedValue({ total: 5, saved_objects: [], @@ -66,7 +69,10 @@ describe('configuration', () => { }); it('it returns the correct res', async () => { - const res = await getConfigurationTelemetryData({ savedObjectsClient, logger }); + const res = await getConfigurationTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { closure: { @@ -82,7 +88,10 @@ describe('configuration', () => { }); it('should call find with correct arguments', async () => { - await getConfigurationTelemetryData({ savedObjectsClient, logger }); + await getConfigurationTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(savedObjectsClient.find).toBeCalledWith({ aggs: { closureType: { @@ -95,6 +104,7 @@ describe('configuration', () => { page: 1, perPage: 5, type: 'cases-configure', + namespaces: ['*'], }); }); @@ -135,7 +145,10 @@ describe('configuration', () => { }, }); - const res = await getConfigurationTelemetryData({ savedObjectsClient, logger }); + const res = await getConfigurationTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { closure: { @@ -205,7 +218,10 @@ describe('configuration', () => { }, }); - const res = await getConfigurationTelemetryData({ savedObjectsClient, logger }); + const res = await getConfigurationTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { closure: { diff --git a/x-pack/plugins/cases/server/telemetry/queries/configuration.ts b/x-pack/plugins/cases/server/telemetry/queries/configuration.ts index e3aff3216f5d5..6b736761207c8 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/configuration.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/configuration.ts @@ -28,6 +28,7 @@ export const getConfigurationTelemetryData = async ({ page: 1, perPage: 5, type: CASE_CONFIGURE_SAVED_OBJECT, + namespaces: ['*'], aggs: { closureType: { terms: { field: `${CASE_CONFIGURE_SAVED_OBJECT}.attributes.closure_type` }, diff --git a/x-pack/plugins/cases/server/telemetry/queries/connectors.test.ts b/x-pack/plugins/cases/server/telemetry/queries/connectors.test.ts index 684c77bac159a..03779f8714d8d 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/connectors.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/connectors.test.ts @@ -7,11 +7,13 @@ import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { getConnectorsTelemetryData } from './connectors'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('getConnectorsTelemetryData', () => { describe('getConnectorsTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); const mockFind = (aggs: Record) => { savedObjectsClient.find.mockResolvedValueOnce({ @@ -42,7 +44,10 @@ describe('getConnectorsTelemetryData', () => { it('it returns the correct res', async () => { mockResponse(); - const res = await getConnectorsTelemetryData({ savedObjectsClient, logger }); + const res = await getConnectorsTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { all: { @@ -71,7 +76,7 @@ describe('getConnectorsTelemetryData', () => { it('should call find with correct arguments', async () => { mockResponse(); - await getConnectorsTelemetryData({ savedObjectsClient, logger }); + await getConnectorsTelemetryData({ savedObjectsClient: telemetrySavedObjectsClient, logger }); expect(savedObjectsClient.find.mock.calls[0][0]).toEqual({ aggs: { @@ -101,6 +106,7 @@ describe('getConnectorsTelemetryData', () => { page: 0, perPage: 0, type: 'cases-user-actions', + namespaces: ['*'], }); expect(savedObjectsClient.find.mock.calls[1][0]).toEqual({ @@ -151,6 +157,7 @@ describe('getConnectorsTelemetryData', () => { page: 0, perPage: 0, type: 'cases-user-actions', + namespaces: ['*'], }); for (const [index, connector] of [ @@ -205,6 +212,7 @@ describe('getConnectorsTelemetryData', () => { page: 0, perPage: 0, type: 'cases-user-actions', + namespaces: ['*'], }); } }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/connectors.ts b/x-pack/plugins/cases/server/telemetry/queries/connectors.ts index 0e8b12e1ed192..c3f254fadb4ce 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/connectors.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/connectors.ts @@ -37,6 +37,7 @@ export const getConnectorsTelemetryData = async ({ perPage: 0, filter, type: CASE_USER_ACTION_SAVED_OBJECT, + namespaces: ['*'], aggs: { ...aggs, }, diff --git a/x-pack/plugins/cases/server/telemetry/queries/pushed.test.ts b/x-pack/plugins/cases/server/telemetry/queries/push.test.ts similarity index 82% rename from x-pack/plugins/cases/server/telemetry/queries/pushed.test.ts rename to x-pack/plugins/cases/server/telemetry/queries/push.test.ts index e25718f0feac9..1834c5f5d54c0 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/pushed.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/push.test.ts @@ -6,12 +6,15 @@ */ import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { getPushedTelemetryData } from './pushes'; +import { getPushedTelemetryData } from './push'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; -describe('pushes', () => { +describe('push', () => { describe('getPushedTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + savedObjectsClient.find.mockResolvedValue({ total: 5, saved_objects: [], @@ -27,7 +30,10 @@ describe('pushes', () => { }); it('it returns the correct res', async () => { - const res = await getPushedTelemetryData({ savedObjectsClient, logger }); + const res = await getPushedTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { maxOnACase: 1, @@ -37,7 +43,7 @@ describe('pushes', () => { }); it('should call find with correct arguments', async () => { - await getPushedTelemetryData({ savedObjectsClient, logger }); + await getPushedTelemetryData({ savedObjectsClient: telemetrySavedObjectsClient, logger }); expect(savedObjectsClient.find).toBeCalledWith({ aggs: { references: { @@ -86,6 +92,7 @@ describe('pushes', () => { page: 0, perPage: 0, type: 'cases-user-actions', + namespaces: ['*'], }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/pushes.ts b/x-pack/plugins/cases/server/telemetry/queries/push.ts similarity index 98% rename from x-pack/plugins/cases/server/telemetry/queries/pushes.ts rename to x-pack/plugins/cases/server/telemetry/queries/push.ts index 0462a7ff0ef13..ea1127ae4520b 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/pushes.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/push.ts @@ -29,6 +29,7 @@ export const getPushedTelemetryData = async ({ perPage: 0, filter: pushFilter, type: CASE_USER_ACTION_SAVED_OBJECT, + namespaces: ['*'], aggs: { ...getMaxBucketOnCaseAggregationQuery(CASE_USER_ACTION_SAVED_OBJECT) }, }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/user_actions.test.ts b/x-pack/plugins/cases/server/telemetry/queries/user_actions.test.ts index c01c8d329c5b0..b6c45d8da3efc 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/user_actions.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/user_actions.test.ts @@ -7,11 +7,14 @@ import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { getUserActionsTelemetryData } from './user_actions'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('user_actions', () => { describe('getUserActionsTelemetryData', () => { const logger = loggingSystemMock.createLogger(); const savedObjectsClient = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + savedObjectsClient.find.mockResolvedValue({ total: 5, saved_objects: [], @@ -34,7 +37,10 @@ describe('user_actions', () => { }); it('it returns the correct res', async () => { - const res = await getUserActionsTelemetryData({ savedObjectsClient, logger }); + const res = await getUserActionsTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(res).toEqual({ all: { total: 5, @@ -47,7 +53,10 @@ describe('user_actions', () => { }); it('should call find with correct arguments', async () => { - await getUserActionsTelemetryData({ savedObjectsClient, logger }); + await getUserActionsTelemetryData({ + savedObjectsClient: telemetrySavedObjectsClient, + logger, + }); expect(savedObjectsClient.find).toBeCalledWith({ aggs: { counts: { @@ -101,6 +110,7 @@ describe('user_actions', () => { page: 0, perPage: 0, type: 'cases-user-actions', + namespaces: ['*'], }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts index bf975b84f46c5..6c66c5aab81c7 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.test.ts @@ -29,6 +29,7 @@ import { getReferencesAggregationQuery, getSolutionValues, } from './utils'; +import { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; describe('utils', () => { describe('getSolutionValues', () => { @@ -1017,7 +1018,12 @@ describe('utils', () => { }); it('returns the correct counts and max data', async () => { - const res = await getCountsAndMaxData({ savedObjectsClient, savedObjectType: 'test' }); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + + const res = await getCountsAndMaxData({ + savedObjectsClient: telemetrySavedObjectsClient, + savedObjectType: 'test', + }); expect(res).toEqual({ all: { total: 5, @@ -1030,6 +1036,7 @@ describe('utils', () => { }); it('returns zero data if the response aggregation is not as expected', async () => { + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); savedObjectsClient.find.mockResolvedValue({ total: 5, saved_objects: [], @@ -1037,7 +1044,10 @@ describe('utils', () => { page: 1, }); - const res = await getCountsAndMaxData({ savedObjectsClient, savedObjectType: 'test' }); + const res = await getCountsAndMaxData({ + savedObjectsClient: telemetrySavedObjectsClient, + savedObjectType: 'test', + }); expect(res).toEqual({ all: { total: 5, @@ -1050,7 +1060,13 @@ describe('utils', () => { }); it('should call find with correct arguments', async () => { - await getCountsAndMaxData({ savedObjectsClient, savedObjectType: 'test' }); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsClient); + + await getCountsAndMaxData({ + savedObjectsClient: telemetrySavedObjectsClient, + savedObjectType: 'test', + }); + expect(savedObjectsClient.find).toBeCalledWith({ aggs: { counts: { @@ -1104,6 +1120,7 @@ describe('utils', () => { page: 0, perPage: 0, type: 'test', + namespaces: ['*'], }); }); }); diff --git a/x-pack/plugins/cases/server/telemetry/queries/utils.ts b/x-pack/plugins/cases/server/telemetry/queries/utils.ts index ff785077d74ac..65b81e3362300 100644 --- a/x-pack/plugins/cases/server/telemetry/queries/utils.ts +++ b/x-pack/plugins/cases/server/telemetry/queries/utils.ts @@ -7,7 +7,6 @@ import { get } from 'lodash'; import type { KueryNode } from '@kbn/es-query'; -import type { ISavedObjectsRepository } from '@kbn/core/server'; import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, @@ -32,6 +31,7 @@ import type { import { buildFilter } from '../../client/utils'; import type { Owner } from '../../../common/constants/types'; import type { ConfigurationPersistedAttributes } from '../../common/types/configure'; +import type { TelemetrySavedObjectsClient } from '../telemetry_saved_objects_client'; export const getCountsAggregationQuery = (savedObjectType: string) => ({ counts: { @@ -126,7 +126,7 @@ export const getCountsAndMaxData = async ({ savedObjectType, filter, }: { - savedObjectsClient: ISavedObjectsRepository; + savedObjectsClient: TelemetrySavedObjectsClient; savedObjectType: string; filter?: KueryNode; }) => { @@ -138,6 +138,7 @@ export const getCountsAndMaxData = async ({ perPage: 0, filter, type: savedObjectType, + namespaces: ['*'], aggs: { ...getCountsAggregationQuery(savedObjectType), ...getMaxBucketOnCaseAggregationQuery(savedObjectType), diff --git a/x-pack/plugins/cases/server/telemetry/telemetry_saved_objects_client.test.ts b/x-pack/plugins/cases/server/telemetry/telemetry_saved_objects_client.test.ts new file mode 100644 index 0000000000000..bbe2d58a1ce9b --- /dev/null +++ b/x-pack/plugins/cases/server/telemetry/telemetry_saved_objects_client.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client'; +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; + +describe('TelemetrySavedObjectsClient', () => { + it("find requests are extended with `namespaces:['*']`", async () => { + const savedObjectsRepository = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsRepository); + + await telemetrySavedObjectsClient.find({ type: 'my-test-type' }); + expect(savedObjectsRepository.find).toBeCalledWith({ type: 'my-test-type', namespaces: ['*'] }); + }); + + it("allow callers to overwrite the `namespaces:['*']`", async () => { + const savedObjectsRepository = savedObjectsRepositoryMock.create(); + const telemetrySavedObjectsClient = new TelemetrySavedObjectsClient(savedObjectsRepository); + + await telemetrySavedObjectsClient.find({ type: 'my-test-type', namespaces: ['some_space'] }); + expect(savedObjectsRepository.find).toBeCalledWith({ + type: 'my-test-type', + namespaces: ['some_space'], + }); + }); +}); diff --git a/x-pack/plugins/cases/server/telemetry/telemetry_saved_objects_client.ts b/x-pack/plugins/cases/server/telemetry/telemetry_saved_objects_client.ts new file mode 100644 index 0000000000000..42ae1fdd296d4 --- /dev/null +++ b/x-pack/plugins/cases/server/telemetry/telemetry_saved_objects_client.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { SavedObjectsFindOptions, SavedObjectsFindResponse } from '@kbn/core/server'; +import { SavedObjectsClient } from '@kbn/core/server'; + +/** + * Extends the SavedObjectsClient to fit the telemetry fetching requirements (i.e.: find objects from all namespaces by default) + */ +export class TelemetrySavedObjectsClient extends SavedObjectsClient { + /** + * Find the SavedObjects matching the search query in all the Spaces by default + * @param options + */ + async find( + options: SavedObjectsFindOptions + ): Promise> { + return super.find({ namespaces: ['*'], ...options }); + } +} diff --git a/x-pack/plugins/cases/server/telemetry/types.ts b/x-pack/plugins/cases/server/telemetry/types.ts index 294efdbce1125..b4996da27f234 100644 --- a/x-pack/plugins/cases/server/telemetry/types.ts +++ b/x-pack/plugins/cases/server/telemetry/types.ts @@ -5,9 +5,10 @@ * 2.0. */ -import type { ISavedObjectsRepository, Logger } from '@kbn/core/server'; +import type { Logger } from '@kbn/core/server'; import type { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server'; import type { Owner } from '../../common/constants/types'; +import type { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client'; export type BucketKeyString = Omit & { key: string }; @@ -35,7 +36,7 @@ export interface ReferencesAggregation { } export interface CollectTelemetryDataParams { - savedObjectsClient: ISavedObjectsRepository; + savedObjectsClient: TelemetrySavedObjectsClient; logger: Logger; } diff --git a/x-pack/plugins/cloud/common/constants.ts b/x-pack/plugins/cloud/common/constants.ts index f2009223f8ac1..27d755ab08214 100644 --- a/x-pack/plugins/cloud/common/constants.ts +++ b/x-pack/plugins/cloud/common/constants.ts @@ -11,3 +11,5 @@ export const ELASTIC_SUPPORT_LINK = 'https://support.elastic.co/'; * This is the page for managing your snapshots on Cloud. */ export const CLOUD_SNAPSHOTS_PATH = 'elasticsearch/snapshots/'; + +export const ELASTICSEARCH_CONFIG_ROUTE = '/api/internal/cloud/elasticsearch_config'; diff --git a/x-pack/plugins/cloud/common/types.ts b/x-pack/plugins/cloud/common/types.ts index ac4593fd0e259..0f72caf515058 100644 --- a/x-pack/plugins/cloud/common/types.ts +++ b/x-pack/plugins/cloud/common/types.ts @@ -6,3 +6,7 @@ */ export type OnBoardingDefaultSolution = 'es' | 'oblt' | 'security'; + +export interface ElasticsearchConfigType { + elasticsearch_url?: string; +} diff --git a/x-pack/plugins/cloud/public/mocks.tsx b/x-pack/plugins/cloud/public/mocks.tsx index dd5c5eced618a..b9f6d850b9acf 100644 --- a/x-pack/plugins/cloud/public/mocks.tsx +++ b/x-pack/plugins/cloud/public/mocks.tsx @@ -19,7 +19,9 @@ function createSetupMock(): jest.Mocked { deploymentUrl: 'deployment-url', profileUrl: 'profile-url', organizationUrl: 'organization-url', - elasticsearchUrl: 'elasticsearch-url', + fetchElasticsearchConfig: jest + .fn() + .mockResolvedValue({ elasticsearchUrl: 'elasticsearch-url' }), kibanaUrl: 'kibana-url', cloudHost: 'cloud-host', cloudDefaultPort: '443', @@ -53,6 +55,7 @@ const createStartMock = (): jest.Mocked => ({ serverless: { projectId: undefined, }, + fetchElasticsearchConfig: jest.fn().mockResolvedValue({ elasticsearchUrl: 'elasticsearch-url' }), }); export const cloudMock = { diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index 2c32ac8fe972a..583d274db77d8 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -37,6 +37,7 @@ describe('Cloud Plugin', () => { const plugin = new CloudPlugin(initContext); const coreSetup = coreMock.createSetup(); + coreSetup.http.get.mockResolvedValue({ elasticsearch_url: 'elasticsearch-url' }); const setup = plugin.setup(coreSetup); return { setup }; @@ -110,8 +111,8 @@ describe('Cloud Plugin', () => { it('exposes components decoded from the cloudId', () => { const decodedId: DecodedCloudId = { defaultPort: '9000', - host: 'host', elasticsearchUrl: 'elasticsearch-url', + host: 'host', kibanaUrl: 'kibana-url', }; decodeCloudIdMock.mockReturnValue(decodedId); @@ -120,7 +121,6 @@ describe('Cloud Plugin', () => { expect.objectContaining({ cloudDefaultPort: '9000', cloudHost: 'host', - elasticsearchUrl: 'elasticsearch-url', kibanaUrl: 'kibana-url', }) ); @@ -184,6 +184,11 @@ describe('Cloud Plugin', () => { }); expect(setup.serverless.projectType).toBe('security'); }); + it('exposes fetchElasticsearchConfig', async () => { + const { setup } = setupPlugin(); + const result = await setup.fetchElasticsearchConfig(); + expect(result).toEqual({ elasticsearchUrl: 'elasticsearch-url' }); + }); }); }); @@ -307,5 +312,13 @@ describe('Cloud Plugin', () => { const start = plugin.start(coreStart); expect(start.serverless.projectName).toBe('My Awesome Project'); }); + it('exposes fetchElasticsearchConfig', async () => { + const { plugin } = startPlugin(); + const coreStart = coreMock.createStart(); + coreStart.http.get.mockResolvedValue({ elasticsearch_url: 'elasticsearch-url' }); + const start = plugin.start(coreStart); + const result = await start.fetchElasticsearchConfig(); + expect(result).toEqual({ elasticsearchUrl: 'elasticsearch-url' }); + }); }); }); diff --git a/x-pack/plugins/cloud/public/plugin.tsx b/x-pack/plugins/cloud/public/plugin.tsx index a661933955060..e89e63dc1c15b 100644 --- a/x-pack/plugins/cloud/public/plugin.tsx +++ b/x-pack/plugins/cloud/public/plugin.tsx @@ -12,12 +12,13 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kb import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url'; -import { CLOUD_SNAPSHOTS_PATH } from '../common/constants'; +import { CLOUD_SNAPSHOTS_PATH, ELASTICSEARCH_CONFIG_ROUTE } from '../common/constants'; import { decodeCloudId, type DecodedCloudId } from '../common/decode_cloud_id'; import { getFullCloudUrl } from '../common/utils'; import { parseOnboardingSolution } from '../common/parse_onboarding_default_solution'; -import type { CloudSetup, CloudStart } from './types'; +import type { CloudSetup, CloudStart, PublicElasticsearchConfigType } from './types'; import { getSupportUrl } from './utils'; +import { ElasticsearchConfigType } from '../common/types'; export interface CloudConfigType { id?: string; @@ -66,12 +67,14 @@ export class CloudPlugin implements Plugin { private readonly isServerlessEnabled: boolean; private readonly contextProviders: Array>> = []; private readonly logger: Logger; + private elasticsearchConfig?: PublicElasticsearchConfigType; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); this.isCloudEnabled = getIsCloudEnabled(this.config.id); this.isServerlessEnabled = !!this.config.serverless?.project_id; this.logger = initializerContext.logger.get(); + this.elasticsearchConfig = undefined; } public setup(core: CoreSetup): CloudSetup { @@ -99,7 +102,6 @@ export class CloudPlugin implements Plugin { csp, baseUrl, ...this.getCloudUrls(), - elasticsearchUrl: decodedId?.elasticsearchUrl, kibanaUrl: decodedId?.kibanaUrl, cloudHost: decodedId?.host, cloudDefaultPort: decodedId?.defaultPort, @@ -119,6 +121,7 @@ export class CloudPlugin implements Plugin { registerCloudService: (contextProvider) => { this.contextProviders.push(contextProvider); }, + fetchElasticsearchConfig: this.fetchElasticsearchConfig.bind(this, core.http), }; } @@ -166,7 +169,6 @@ export class CloudPlugin implements Plugin { profileUrl, organizationUrl, projectsUrl, - elasticsearchUrl: decodedId?.elasticsearchUrl, kibanaUrl: decodedId?.kibanaUrl, isServerlessEnabled: this.isServerlessEnabled, serverless: { @@ -176,6 +178,7 @@ export class CloudPlugin implements Plugin { }, performanceUrl, usersAndRolesUrl, + fetchElasticsearchConfig: this.fetchElasticsearchConfig.bind(this, coreStart.http), }; } @@ -216,4 +219,26 @@ export class CloudPlugin implements Plugin { projectsUrl: fullCloudProjectsUrl, }; } + + private async fetchElasticsearchConfig( + http: CoreStart['http'] + ): Promise { + if (this.elasticsearchConfig !== undefined) { + // This config should be fully populated on first fetch, so we should avoid refetching from server + return this.elasticsearchConfig; + } + try { + const result = await http.get(ELASTICSEARCH_CONFIG_ROUTE, { + version: '1', + }); + + this.elasticsearchConfig = { elasticsearchUrl: result.elasticsearch_url || undefined }; + return this.elasticsearchConfig; + } catch { + this.logger.error('Failed to fetch Elasticsearch config'); + return { + elasticsearchUrl: undefined, + }; + } + } } diff --git a/x-pack/plugins/cloud/public/types.ts b/x-pack/plugins/cloud/public/types.ts index 8df1ba645cb48..1428e887f1b9f 100644 --- a/x-pack/plugins/cloud/public/types.ts +++ b/x-pack/plugins/cloud/public/types.ts @@ -58,9 +58,9 @@ export interface CloudStart { */ projectsUrl?: string; /** - * The full URL to the elasticsearch cluster. + * Fetches the full URL to the elasticsearch cluster. */ - elasticsearchUrl?: string; + fetchElasticsearchConfig: () => Promise; /** * The full URL to the Kibana deployment. */ @@ -150,9 +150,9 @@ export interface CloudSetup { */ snapshotsUrl?: string; /** - * The full URL to the elasticsearch cluster. + * Fetches the full URL to the elasticsearch cluster. */ - elasticsearchUrl?: string; + fetchElasticsearchConfig: () => Promise; /** * The full URL to the Kibana deployment. */ @@ -225,3 +225,12 @@ export interface CloudSetup { orchestratorTarget?: string; }; } + +export interface PublicElasticsearchConfigType { + /** + * The URL to the Elasticsearch cluster, derived from xpack.elasticsearch.publicBaseUrl if populated + * Otherwise this is based on the cloudId + * If neither is populated, this will be undefined + */ + elasticsearchUrl?: string; +} diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index a03878b760dd4..9f45b5398ac22 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -18,6 +18,7 @@ import { decodeCloudId, DecodedCloudId } from '../common/decode_cloud_id'; import { parseOnboardingSolution } from '../common/parse_onboarding_default_solution'; import { getFullCloudUrl } from '../common/utils'; import { readInstanceSizeMb } from './env'; +import { defineRoutes } from './routes/elasticsearch_routes'; interface PluginsSetup { usageCollection?: UsageCollectionSetup; @@ -201,6 +202,9 @@ export class CloudPlugin implements Plugin { if (this.config.id) { decodedId = decodeCloudId(this.config.id, this.logger); } + const router = core.http.createRouter(); + const elasticsearchUrl = core.elasticsearch.publicBaseUrl || decodedId?.elasticsearchUrl; + defineRoutes({ logger: this.logger, router, elasticsearchUrl }); return { ...this.getCloudUrls(), @@ -209,7 +213,7 @@ export class CloudPlugin implements Plugin { organizationId, instanceSizeMb: readInstanceSizeMb(), deploymentId, - elasticsearchUrl: core.elasticsearch.publicBaseUrl || decodedId?.elasticsearchUrl, + elasticsearchUrl, kibanaUrl: decodedId?.kibanaUrl, cloudHost: decodedId?.host, cloudDefaultPort: decodedId?.defaultPort, diff --git a/x-pack/plugins/cloud/server/routes/elasticsearch_routes.ts b/x-pack/plugins/cloud/server/routes/elasticsearch_routes.ts new file mode 100644 index 0000000000000..5cdc2f90559cc --- /dev/null +++ b/x-pack/plugins/cloud/server/routes/elasticsearch_routes.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IRouter } from '@kbn/core/server'; +import { Logger } from '@kbn/logging'; +import { ElasticsearchConfigType } from '../../common/types'; +import { ELASTICSEARCH_CONFIG_ROUTE } from '../../common/constants'; + +export function defineRoutes({ + elasticsearchUrl, + logger, + router, +}: { + elasticsearchUrl?: string; + logger: Logger; + router: IRouter; +}) { + router.versioned + .get({ + path: ELASTICSEARCH_CONFIG_ROUTE, + access: 'internal', + }) + .addVersion({ version: '1', validate: {} }, async (context, request, response) => { + const body: ElasticsearchConfigType = { + elasticsearch_url: elasticsearchUrl, + }; + return response.ok({ + body, + }); + }); +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx index 6671e4c73c2e8..8e3d48c47c789 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx @@ -266,6 +266,7 @@ export const IndexDataVisualizerESQL: FC = (dataVi detectedTimestamp={currentDataView?.timeFieldName} hideRunQueryText={false} isLoading={queryHistoryStatus ?? false} + displayDocumentationAsFlyout /> diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx index 9bb23b677f743..f55034f72ccd6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/analytics_collection_integrate.test.tsx @@ -22,6 +22,12 @@ jest.mock('../../../../shared/enterprise_search_url', () => ({ getEnterpriseSearchUrl: () => 'http://localhost:3002', })); +jest.mock('../../../../shared/cloud_details/cloud_details', () => ({ + useCloudDetails: () => ({ + elasticsearchUrl: 'your_deployment_url', + }), +})); + describe('AnalyticsCollectionIntegrate', () => { const analyticsCollections: AnalyticsCollection = { events_datastream: 'analytics-events-example', @@ -55,7 +61,7 @@ describe('AnalyticsCollectionIntegrate', () => { .toMatchInlineSnapshot(` "