From e352943ee864df6bd551e4fffb1d5ffb7a7a19ae Mon Sep 17 00:00:00 2001 From: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> Date: Wed, 3 Jan 2024 14:19:18 +0000 Subject: [PATCH 01/18] [DOCS] Update tech preview to warn against using ES|QL in production (#174109) ## Summary Adds the sentence "Do not use ES|QL on production environments." to the ES|QL tech preview banner on pages: https://www.elastic.co/guide/en/kibana/current/esql.html https://www.elastic.co/guide/en/kibana/current/try-esql.html Relates to: [#103797](https://github.com/elastic/elasticsearch/pull/103797) --- docs/concepts/esql.asciidoc | 2 +- docs/discover/try-esql.asciidoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/concepts/esql.asciidoc b/docs/concepts/esql.asciidoc index 413db3ed35bbc6..97f4b825863224 100644 --- a/docs/concepts/esql.asciidoc +++ b/docs/concepts/esql.asciidoc @@ -1,7 +1,7 @@ [[esql]] === {esql} -preview::[] +preview::["Do not use {esql} on production environments. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."] The Elasticsearch Query Language, {esql}, has been created to make exploring your data faster and easier using the **Discover** application. From version 8.11 you can try this new feature, which is enabled by default. diff --git a/docs/discover/try-esql.asciidoc b/docs/discover/try-esql.asciidoc index cc6e4d62f17c45..6c827240c1832d 100644 --- a/docs/discover/try-esql.asciidoc +++ b/docs/discover/try-esql.asciidoc @@ -1,7 +1,7 @@ [[try-esql]] == Try {esql} -preview::[] +preview::["Do not use {esql} on production environments. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."] The Elasticsearch Query Language, {esql}, makes it easier to explore your data without leaving Discover. From 52f458c3f985fb9e67cdc774ee5ed241942a0435 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 3 Jan 2024 15:55:02 +0100 Subject: [PATCH 02/18] feat(slo): Add error budget remaining in time unit (#173662) --- .../components/error_budget_chart_panel.tsx | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.tsx index a053e39fae34ca..b47cf7bf3f89eb 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/error_budget_chart_panel.tsx @@ -22,28 +22,41 @@ export interface Props { slo: SLOWithSummaryResponse; } +function formatTime(minutes: number) { + if (minutes > 59) { + const mins = minutes % 60; + const hours = (minutes - mins) / 60; + return i18n.translate( + 'xpack.observability.slo.sloDetails.errorBudgetChartPanel.minuteHoursLabel', + { + defaultMessage: '{hours}h {mins}m', + values: { hours: Math.trunc(hours), mins: Math.trunc(mins) }, + } + ); + } + return i18n.translate('xpack.observability.slo.sloDetails.errorBudgetChartPanel.minuteLabel', { + defaultMessage: '{minutes}m', + values: { minutes }, + }); +} + export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) { const { uiSettings } = useKibana().services; const percentFormat = uiSettings.get('format:percent:defaultPattern'); const isSloFailed = slo.summary.status === 'DEGRADING' || slo.summary.status === 'VIOLATED'; - let remainingBudgetFormatted; + let errorBudgetTimeRemainingFormatted; if (slo.budgetingMethod === 'timeslices' && slo.timeWindow.type === 'calendarAligned') { const totalSlices = toMinutes(toDuration(slo.timeWindow.duration)) / toMinutes(toDuration(slo.objective.timesliceWindow!)); - const remainingBudgetInTimeUnit = + const errorBudgetRemainingInMinute = slo.summary.errorBudget.remaining * (slo.summary.errorBudget.initial * totalSlices); - if (remainingBudgetInTimeUnit <= 0) { - remainingBudgetFormatted = '0min'; - } else { - if (remainingBudgetInTimeUnit / 60 >= 1) { - remainingBudgetFormatted = `${Math.trunc(remainingBudgetInTimeUnit / 60)}h`; - } - remainingBudgetFormatted += `${Math.trunc(remainingBudgetInTimeUnit % 60)}min`; - } + errorBudgetTimeRemainingFormatted = formatTime( + errorBudgetRemainingInMinute >= 0 ? errorBudgetRemainingInMinute : 0 + ); } return ( @@ -87,11 +100,11 @@ export function ErrorBudgetChartPanel({ data, isLoading, slo }: Props) { reverse /> - {!!remainingBudgetFormatted && ( + {errorBudgetTimeRemainingFormatted ? ( - )} + ) : null} From 085da724c99e0c141df4203e7601145002288c7b Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 3 Jan 2024 16:07:36 +0100 Subject: [PATCH 03/18] [ML] Link to Anomaly detection job creation from the alerting rule form (#174016) ## Summary Closes https://github.com/elastic/kibana/issues/173654 Adds an option to create a new anomaly detection job from the job selector control. image ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../advanced_settings.tsx | 6 +- .../config_validator.tsx | 12 +- .../alerting/anomaly_detection_rule/index.ts | 8 + .../interim_results_control.tsx | 0 .../ml_anomaly_alert_trigger.tsx | 62 ++++++-- .../preview_alert_condition.tsx | 12 +- .../register_anomaly_detection_rule.tsx | 145 ++++++++++++++++++ .../result_type_selector.tsx | 0 .../ml/public/alerting/job_selector.tsx | 65 +++++++- .../ml/public/alerting/register_ml_alerts.ts | 118 +------------- x-pack/plugins/ml/public/plugin.ts | 1 + 11 files changed, 278 insertions(+), 151 deletions(-) rename x-pack/plugins/ml/public/alerting/{ => anomaly_detection_rule}/advanced_settings.tsx (93%) rename x-pack/plugins/ml/public/alerting/{ => anomaly_detection_rule}/config_validator.tsx (90%) create mode 100644 x-pack/plugins/ml/public/alerting/anomaly_detection_rule/index.ts rename x-pack/plugins/ml/public/alerting/{ => anomaly_detection_rule}/interim_results_control.tsx (100%) rename x-pack/plugins/ml/public/alerting/{ => anomaly_detection_rule}/ml_anomaly_alert_trigger.tsx (78%) rename x-pack/plugins/ml/public/alerting/{ => anomaly_detection_rule}/preview_alert_condition.tsx (96%) create mode 100644 x-pack/plugins/ml/public/alerting/anomaly_detection_rule/register_anomaly_detection_rule.tsx rename x-pack/plugins/ml/public/alerting/{ => anomaly_detection_rule}/result_type_selector.tsx (100%) diff --git a/x-pack/plugins/ml/public/alerting/advanced_settings.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/advanced_settings.tsx similarity index 93% rename from x-pack/plugins/ml/public/alerting/advanced_settings.tsx rename to x-pack/plugins/ml/public/alerting/anomaly_detection_rule/advanced_settings.tsx index 093c3d615a510d..f3f2f3b0427809 100644 --- a/x-pack/plugins/ml/public/alerting/advanced_settings.tsx +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/advanced_settings.tsx @@ -16,9 +16,9 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { MlAnomalyDetectionAlertAdvancedSettings } from '../../common/types/alerts'; -import { TimeIntervalControl } from './time_interval_control'; -import { TOP_N_BUCKETS_COUNT } from '../../common/constants/alerts'; +import { type MlAnomalyDetectionAlertAdvancedSettings } from '../../../common/types/alerts'; +import { TimeIntervalControl } from '../time_interval_control'; +import { TOP_N_BUCKETS_COUNT } from '../../../common/constants/alerts'; interface AdvancedSettingsProps { value: MlAnomalyDetectionAlertAdvancedSettings; diff --git a/x-pack/plugins/ml/public/alerting/config_validator.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/config_validator.tsx similarity index 90% rename from x-pack/plugins/ml/public/alerting/config_validator.tsx rename to x-pack/plugins/ml/public/alerting/anomaly_detection_rule/config_validator.tsx index 3afc02dc60600f..c7d9187e350574 100644 --- a/x-pack/plugins/ml/public/alerting/config_validator.tsx +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/config_validator.tsx @@ -9,12 +9,12 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { parseInterval } from '../../common/util/parse_interval'; -import { CombinedJobWithStats } from '../../common/types/anomaly_detection_jobs'; -import { DATAFEED_STATE } from '../../common/constants/states'; -import { MlAnomalyDetectionAlertParams } from '../../common/types/alerts'; -import { MlAnomalyAlertTriggerProps } from './ml_anomaly_alert_trigger'; -import { TOP_N_BUCKETS_COUNT } from '../../common/constants/alerts'; +import { parseInterval } from '../../../common/util/parse_interval'; +import { type CombinedJobWithStats } from '../../../common/types/anomaly_detection_jobs'; +import { DATAFEED_STATE } from '../../../common/constants/states'; +import { type MlAnomalyDetectionAlertParams } from '../../../common/types/alerts'; +import { type MlAnomalyAlertTriggerProps } from './ml_anomaly_alert_trigger'; +import { TOP_N_BUCKETS_COUNT } from '../../../common/constants/alerts'; interface ConfigValidatorProps { alertInterval: string; diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/index.ts b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/index.ts new file mode 100644 index 00000000000000..0e710651a0c68a --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { registerAnomalyDetectionRule } from './register_anomaly_detection_rule'; diff --git a/x-pack/plugins/ml/public/alerting/interim_results_control.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/interim_results_control.tsx similarity index 100% rename from x-pack/plugins/ml/public/alerting/interim_results_control.tsx rename to x-pack/plugins/ml/public/alerting/anomaly_detection_rule/interim_results_control.tsx diff --git a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx similarity index 78% rename from x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx rename to x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx index 45bf7b1ca613bb..547ec627decf27 100644 --- a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx @@ -12,28 +12,34 @@ import { i18n } from '@kbn/i18n'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { isDefined } from '@kbn/ml-is-defined'; import { ML_ANOMALY_RESULT_TYPE, ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils'; -import { JobSelectorControl } from './job_selector'; -import { useMlKibana } from '../application/contexts/kibana'; -import { jobsApiProvider } from '../application/services/ml_api_service/jobs'; -import { HttpService } from '../application/services/http_service'; -import { useToastNotificationService } from '../application/services/toast_notification_service'; -import { SeverityControl } from '../application/components/severity_control'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { MlCapabilities } from '../../../common/types/capabilities'; +import { ML_PAGES } from '../../../common/constants/locator'; +import type { MlCoreSetup } from '../../plugin'; +import { JobSelectorControl } from '../job_selector'; +import { jobsApiProvider } from '../../application/services/ml_api_service/jobs'; +import { HttpService } from '../../application/services/http_service'; +import { useToastNotificationService } from '../../application/services/toast_notification_service'; +import { SeverityControl } from '../../application/components/severity_control'; import { ResultTypeSelector } from './result_type_selector'; -import { alertingApiProvider } from '../application/services/ml_api_service/alerting'; +import { alertingApiProvider } from '../../application/services/ml_api_service/alerting'; import { PreviewAlertCondition } from './preview_alert_condition'; -import { +import type { MlAnomalyDetectionAlertAdvancedSettings, MlAnomalyDetectionAlertParams, -} from '../../common/types/alerts'; +} from '../../../common/types/alerts'; import { InterimResultsControl } from './interim_results_control'; import { ConfigValidator } from './config_validator'; -import { CombinedJobWithStats } from '../../common/types/anomaly_detection_jobs'; +import { type CombinedJobWithStats } from '../../../common/types/anomaly_detection_jobs'; import { AdvancedSettings } from './advanced_settings'; -import { getLookbackInterval, getTopNBuckets } from '../../common/util/alerts'; -import { parseInterval } from '../../common/util/parse_interval'; +import { getLookbackInterval, getTopNBuckets } from '../../../common/util/alerts'; +import { parseInterval } from '../../../common/util/parse_interval'; export type MlAnomalyAlertTriggerProps = - RuleTypeParamsExpressionProps; + RuleTypeParamsExpressionProps & { + getStartServices: MlCoreSetup['getStartServices']; + mlCapabilities: MlCapabilities; + }; const MlAnomalyAlertTrigger: FC = ({ ruleParams, @@ -42,11 +48,36 @@ const MlAnomalyAlertTrigger: FC = ({ errors, ruleInterval, alertNotifyWhen, + getStartServices, + mlCapabilities, }) => { const { services: { http }, - } = useMlKibana(); - const mlHttpService = useMemo(() => new HttpService(http), [http]); + } = useKibana(); + + const [newJobUrl, setNewJobUrl] = useState(undefined); + + useEffect(() => { + let mounted = true; + + if (!mlCapabilities.canCreateJob) return; + + getStartServices().then((startServices) => { + const locator = startServices[2].locator; + if (!locator) return; + locator.getUrl({ page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB }).then((url) => { + if (mounted) { + setNewJobUrl(url); + } + }); + }); + + return () => { + mounted = false; + }; + }, [getStartServices, mlCapabilities]); + + const mlHttpService = useMemo(() => new HttpService(http!), [http]); const adJobsApiService = useMemo(() => jobsApiProvider(mlHttpService), [mlHttpService]); const alertingApiService = useMemo(() => alertingApiProvider(mlHttpService), [mlHttpService]); const { displayErrorToast } = useToastNotificationService(); @@ -159,6 +190,7 @@ const MlAnomalyAlertTrigger: FC = ({ return ( ) => { + const MlAlertTrigger = lazy(() => import('./ml_anomaly_alert_trigger')); + return ( + + ); + }, + validate: (ruleParams: MlAnomalyDetectionAlertParams) => { + const validationResult = { + errors: { + jobSelection: new Array(), + severity: new Array(), + resultType: new Array(), + topNBuckets: new Array(), + lookbackInterval: new Array(), + } as Record, + }; + + if (!ruleParams.jobSelection?.jobIds?.length && !ruleParams.jobSelection?.groupIds?.length) { + validationResult.errors.jobSelection.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.jobSelection.errorMessage', { + defaultMessage: 'Job selection is required', + }) + ); + } + + // Since 7.13 we support single job selection only + if ( + (Array.isArray(ruleParams.jobSelection?.groupIds) && + ruleParams.jobSelection?.groupIds.length > 0) || + (Array.isArray(ruleParams.jobSelection?.jobIds) && + ruleParams.jobSelection?.jobIds.length > 1) + ) { + validationResult.errors.jobSelection.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.singleJobSelection.errorMessage', { + defaultMessage: 'Only one job per rule is allowed', + }) + ); + } + + if (ruleParams.severity === undefined) { + validationResult.errors.severity.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.severity.errorMessage', { + defaultMessage: 'Anomaly severity is required', + }) + ); + } + + if (ruleParams.resultType === undefined) { + validationResult.errors.resultType.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.resultType.errorMessage', { + defaultMessage: 'Result type is required', + }) + ); + } + + if (!!ruleParams.lookbackInterval && validateLookbackInterval(ruleParams.lookbackInterval)) { + validationResult.errors.lookbackInterval.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.lookbackInterval.errorMessage', { + defaultMessage: 'Lookback interval is invalid', + }) + ); + } + + if ( + typeof ruleParams.topNBuckets === 'number' && + validateTopNBucket(ruleParams.topNBuckets) + ) { + validationResult.errors.topNBuckets.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.topNBuckets.errorMessage', { + defaultMessage: 'Number of buckets is invalid', + }) + ); + } + + return validationResult; + }, + requiresAppContext: false, + defaultActionMessage: i18n.translate( + 'xpack.ml.alertTypes.anomalyDetection.defaultActionMessage', + { + defaultMessage: `[\\{\\{rule.name\\}\\}] Elastic Stack Machine Learning Alert: +- Job IDs: \\{\\{context.jobIds\\}\\} +- Time: \\{\\{context.timestampIso8601\\}\\} +- Anomaly score: \\{\\{context.score\\}\\} + +\\{\\{context.message\\}\\} + +\\{\\{#context.topInfluencers.length\\}\\} + Top influencers: + \\{\\{#context.topInfluencers\\}\\} + \\{\\{influencer_field_name\\}\\} = \\{\\{influencer_field_value\\}\\} [\\{\\{score\\}\\}] + \\{\\{/context.topInfluencers\\}\\} +\\{\\{/context.topInfluencers.length\\}\\} + +\\{\\{#context.topRecords.length\\}\\} + Top records: + \\{\\{#context.topRecords\\}\\} + \\{\\{function\\}\\}(\\{\\{field_name\\}\\}) \\{\\{by_field_value\\}\\}\\{\\{over_field_value\\}\\}\\{\\{partition_field_value\\}\\} [\\{\\{score\\}\\}]. Typical: \\{\\{typical\\}\\}, Actual: \\{\\{actual\\}\\} + \\{\\{/context.topRecords\\}\\} +\\{\\{/context.topRecords.length\\}\\} + +\\{\\{! Replace kibanaBaseUrl if not configured in Kibana \\}\\} +[Open in Anomaly Explorer](\\{\\{\\{kibanaBaseUrl\\}\\}\\}\\{\\{\\{context.anomalyExplorerUrl\\}\\}\\}) +`, + } + ), + }); +} diff --git a/x-pack/plugins/ml/public/alerting/result_type_selector.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/result_type_selector.tsx similarity index 100% rename from x-pack/plugins/ml/public/alerting/result_type_selector.tsx rename to x-pack/plugins/ml/public/alerting/anomaly_detection_rule/result_type_selector.tsx diff --git a/x-pack/plugins/ml/public/alerting/job_selector.tsx b/x-pack/plugins/ml/public/alerting/job_selector.tsx index 2a4791a29e5766..30662bb4009184 100644 --- a/x-pack/plugins/ml/public/alerting/job_selector.tsx +++ b/x-pack/plugins/ml/public/alerting/job_selector.tsx @@ -9,6 +9,8 @@ import React, { FC, ReactNode, useCallback, useEffect, useMemo, useState } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiComboBox, EuiComboBoxOptionOption, EuiComboBoxProps, EuiFormRow } from '@elastic/eui'; +import useMountedState from 'react-use/lib/useMountedState'; +import { useMlKibana } from '../application/contexts/kibana'; import { JobId } from '../../common/types/anomaly_detection_jobs'; import { MlApiServices } from '../application/services/ml_api_service'; import { ALL_JOBS_SELECTION } from '../../common/constants/alerts'; @@ -33,6 +35,8 @@ export interface JobSelectorControlProps { * Allows selecting all jobs, even those created afterward. */ allowSelectAll?: boolean; + /** Adds an option to create a new anomaly detection job */ + createJobUrl?: string; /** * Available options to select. By default suggest all existing jobs. */ @@ -47,8 +51,18 @@ export const JobSelectorControl: FC = ({ multiSelect = false, label, allowSelectAll = false, + createJobUrl, options: defaultOptions, }) => { + const { + services: { + notifications: { toasts }, + application: { navigateToUrl }, + }, + } = useMlKibana(); + + const isMounted = useMountedState(); + const [options, setOptions] = useState>>([]); const jobIds = useMemo(() => new Set(), []); const groupIds = useMemo(() => new Set(), []); @@ -69,10 +83,13 @@ export const JobSelectorControl: FC = ({ jobIdOptions.forEach((v) => { jobIds.add(v); }); + groupIdOptions.forEach((v) => { groupIds.add(v); }); + if (!isMounted()) return; + setOptions([ ...(allowSelectAll ? [ @@ -91,11 +108,24 @@ export const JobSelectorControl: FC = ({ }, ] : []), + { label: i18n.translate('xpack.ml.jobSelector.jobOptionsLabel', { defaultMessage: 'Jobs', }), - options: jobIdOptions.map((v) => ({ label: v })), + options: [ + ...(createJobUrl + ? [ + { + label: i18n.translate('xpack.ml.jobSelector.createNewLabel', { + defaultMessage: '--- Create new ---', + }), + value: 'createNew', + }, + ] + : []), + ...jobIdOptions.map((v) => ({ label: v })), + ], }, ...(multiSelect ? [ @@ -109,19 +139,40 @@ export const JobSelectorControl: FC = ({ : []), ]); } catch (e) { - // TODO add error handling + toasts.addError(e, { + title: i18n.translate('xpack.ml.jobSelector.fetchJobErrorTitle', { + defaultMessage: 'Failed to load anomaly detection jobs', + }), + }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [adJobsApiService]); + }, [ + adJobsApiService, + allowSelectAll, + createJobUrl, + groupIds, + isMounted, + jobIds, + multiSelect, + toasts, + ]); // eslint-disable-next-line react-hooks/exhaustive-deps const onSelectionChange: EuiComboBoxProps['onChange'] = useCallback( - ((selectionUpdate) => { + (async (selectionUpdate) => { if (selectionUpdate.some((selectedOption) => selectedOption.value === ALL_JOBS_SELECTION)) { onChange({ jobIds: [ALL_JOBS_SELECTION] }); return; } + if ( + !!createJobUrl && + selectionUpdate.some((selectedOption) => selectedOption.value === 'createNew') + ) { + // Redirect to the job wizard page + await navigateToUrl(createJobUrl); + return; + } + const selectedJobIds: JobId[] = []; const selectedGroupIds: string[] = []; selectionUpdate.forEach(({ label: selectedLabel }: { label: string }) => { @@ -138,14 +189,14 @@ export const JobSelectorControl: FC = ({ ...(selectedGroupIds.length > 0 ? { groupIds: selectedGroupIds } : {}), }); }) as Exclude['onChange'], undefined>, - [jobIds, groupIds, defaultOptions] + [jobIds, groupIds, defaultOptions, createJobUrl] ); useEffect(() => { if (defaultOptions) return; fetchOptions(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [createJobUrl]); return ( import('./ml_anomaly_alert_trigger')), - validate: (ruleParams: MlAnomalyDetectionAlertParams) => { - const validationResult = { - errors: { - jobSelection: new Array(), - severity: new Array(), - resultType: new Array(), - topNBuckets: new Array(), - lookbackInterval: new Array(), - } as Record, - }; - - if (!ruleParams.jobSelection?.jobIds?.length && !ruleParams.jobSelection?.groupIds?.length) { - validationResult.errors.jobSelection.push( - i18n.translate('xpack.ml.alertTypes.anomalyDetection.jobSelection.errorMessage', { - defaultMessage: 'Job selection is required', - }) - ); - } - - // Since 7.13 we support single job selection only - if ( - (Array.isArray(ruleParams.jobSelection?.groupIds) && - ruleParams.jobSelection?.groupIds.length > 0) || - (Array.isArray(ruleParams.jobSelection?.jobIds) && - ruleParams.jobSelection?.jobIds.length > 1) - ) { - validationResult.errors.jobSelection.push( - i18n.translate('xpack.ml.alertTypes.anomalyDetection.singleJobSelection.errorMessage', { - defaultMessage: 'Only one job per rule is allowed', - }) - ); - } - - if (ruleParams.severity === undefined) { - validationResult.errors.severity.push( - i18n.translate('xpack.ml.alertTypes.anomalyDetection.severity.errorMessage', { - defaultMessage: 'Anomaly severity is required', - }) - ); - } - - if (ruleParams.resultType === undefined) { - validationResult.errors.resultType.push( - i18n.translate('xpack.ml.alertTypes.anomalyDetection.resultType.errorMessage', { - defaultMessage: 'Result type is required', - }) - ); - } - - if (!!ruleParams.lookbackInterval && validateLookbackInterval(ruleParams.lookbackInterval)) { - validationResult.errors.lookbackInterval.push( - i18n.translate('xpack.ml.alertTypes.anomalyDetection.lookbackInterval.errorMessage', { - defaultMessage: 'Lookback interval is invalid', - }) - ); - } - - if ( - typeof ruleParams.topNBuckets === 'number' && - validateTopNBucket(ruleParams.topNBuckets) - ) { - validationResult.errors.topNBuckets.push( - i18n.translate('xpack.ml.alertTypes.anomalyDetection.topNBuckets.errorMessage', { - defaultMessage: 'Number of buckets is invalid', - }) - ); - } - - return validationResult; - }, - requiresAppContext: false, - defaultActionMessage: i18n.translate( - 'xpack.ml.alertTypes.anomalyDetection.defaultActionMessage', - { - defaultMessage: `[\\{\\{rule.name\\}\\}] Elastic Stack Machine Learning Alert: -- Job IDs: \\{\\{context.jobIds\\}\\} -- Time: \\{\\{context.timestampIso8601\\}\\} -- Anomaly score: \\{\\{context.score\\}\\} - -\\{\\{context.message\\}\\} - -\\{\\{#context.topInfluencers.length\\}\\} - Top influencers: - \\{\\{#context.topInfluencers\\}\\} - \\{\\{influencer_field_name\\}\\} = \\{\\{influencer_field_value\\}\\} [\\{\\{score\\}\\}] - \\{\\{/context.topInfluencers\\}\\} -\\{\\{/context.topInfluencers.length\\}\\} - -\\{\\{#context.topRecords.length\\}\\} - Top records: - \\{\\{#context.topRecords\\}\\} - \\{\\{function\\}\\}(\\{\\{field_name\\}\\}) \\{\\{by_field_value\\}\\}\\{\\{over_field_value\\}\\}\\{\\{partition_field_value\\}\\} [\\{\\{score\\}\\}]. Typical: \\{\\{typical\\}\\}, Actual: \\{\\{actual\\}\\} - \\{\\{/context.topRecords\\}\\} -\\{\\{/context.topRecords.length\\}\\} - -\\{\\{! Replace kibanaBaseUrl if not configured in Kibana \\}\\} -[Open in Anomaly Explorer](\\{\\{\\{kibanaBaseUrl\\}\\}\\}\\{\\{\\{context.anomalyExplorerUrl\\}\\}\\}) -`, - } - ), - }); + registerAnomalyDetectionRule(triggersActionsUi, getStartServices, mlCapabilities); registerJobsHealthAlertingRule(triggersActionsUi, alerting); diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index b476ea10ac72de..441b88b2cc14e6 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -242,6 +242,7 @@ export class MlPlugin implements Plugin { registerMlAlerts( pluginsSetup.triggersActionsUi, core.getStartServices, + mlCapabilities, pluginsSetup.alerting ); } From 76e812133a0486c8e864d2feeea8ef21dbe433ab Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 3 Jan 2024 16:20:06 +0100 Subject: [PATCH 04/18] [UnifiedFieldList] Show drag handle on item hover (#173673) - Resolves https://github.com/elastic/kibana/issues/168856 ## Summary As per comment in https://github.com/elastic/kibana/pull/171572#issuecomment-1841587066 > As a simple way to mitigate this issue, what if we changed it so that on hover/focus of a field list item we fade-out the token and fade-in the drag handle to replace the token (in the same position)? In doing so, we could also keep the old translate x-axis transition (where the field list item slides a few pixels to the right) to emphasize that it's draggable. That little bit of extra movement might be good, if the appearance of the drag handle is no longer pushing the text (given the above suggestion). ![Dec-19-2023 18-13-21](https://github.com/elastic/kibana/assets/1415710/e02cb7d6-ce1a-4507-a8a6-3004f89d7225) ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../field_item_button.test.tsx.snap | 193 +++++++++++++----- .../field_item_button/field_item_button.scss | 35 ++++ .../field_item_button.test.tsx | 6 +- .../field_item_button/field_item_button.tsx | 19 +- .../field_list_item.tsx | 1 + .../public/datasources/common/field_item.tsx | 1 + 6 files changed, 194 insertions(+), 61 deletions(-) diff --git a/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap b/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap index bac19af40c3106..3bedc4320df4fc 100644 --- a/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap +++ b/packages/kbn-unified-field-list/src/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnifiedFieldList renders properly 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly 1`] = ` renders properly 1`] = ` className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists" dataTestSubj="field-bytes-showDetails" fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly 1`] = ` /> `; -exports[`UnifiedFieldList renders properly for Records (Lens field) 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly for Records (Lens field) 1`] = ` renders properly for Records (Lens className="unifiedFieldListItemButton unifiedFieldListItemButton--document unifiedFieldListItemButton--exists" dataTestSubj="field-___records___-showDetails" fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly for Records (Lens /> `; -exports[`UnifiedFieldList renders properly for search with spaces 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly for search with spaces 1`] = ` renders properly for search with s className="unifiedFieldListItemButton unifiedFieldListItemButton--date unifiedFieldListItemButton--exists" dataTestSubj="field-script date-showDetails" fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly for search with s /> `; -exports[`UnifiedFieldList renders properly for text-based column field 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly for text-based column field 1`] = ` renders properly for text-based co className="unifiedFieldListItemButton unifiedFieldListItemButton--string unifiedFieldListItemButton--exists" dataTestSubj="field-agent-showDetails" fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly for text-based co /> `; -exports[`UnifiedFieldList renders properly for wildcard search 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly for wildcard search 1`] = ` renders properly for wildcard sear className="unifiedFieldListItemButton unifiedFieldListItemButton--date unifiedFieldListItemButton--exists" dataTestSubj="field-script date-showDetails" fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly for wildcard sear /> `; -exports[`UnifiedFieldList renders properly when a conflict field 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly when a conflict field 1`] = ` renders properly when a conflict f className="unifiedFieldListItemButton unifiedFieldListItemButton--conflict unifiedFieldListItemButton--exists" dataTestSubj="field-custom_user_field-showDetails" fieldIcon={ - +
+
+ +
+
} fieldInfoIcon={ renders properly when a conflict f /> `; -exports[`UnifiedFieldList renders properly when empty 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly when empty 1`] = ` renders properly when empty 1`] = className="unifiedFieldListItemButton unifiedFieldListItemButton--date unifiedFieldListItemButton--missing" dataTestSubj="field-script date-showDetails" fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly when empty 1`] = /> `; -exports[`UnifiedFieldList renders properly with a drag handle 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly with a drag icon 1`] = ` - dragHandle - - } fieldIcon={ - +
+
+ +
+
+ +
+
} fieldName={ renders properly with a drag handl /> `; -exports[`UnifiedFieldList renders properly with an action when deselected 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly with an action when deselected 1`] = ` renders properly with an action wh } fieldIcon={ - +
+
+ +
+
} fieldName={ renders properly with an action wh /> `; -exports[`UnifiedFieldList renders properly with an action when selected 1`] = ` +exports[`UnifiedFieldList FieldItemButton renders properly with an action when selected 1`] = ` renders properly with an action wh } fieldIcon={ - +
+
+ +
+
} fieldName={ ', () => { +describe('UnifiedFieldList FieldItemButton', () => { test('renders properly', () => { const component = shallow( ', () => { expect(component).toMatchSnapshot(); }); - test('renders properly with a drag handle', () => { + test('renders properly with a drag icon', () => { const component = shallow( dragHandle} + withDragIcon={true} field={bytesField} fieldSearchHighlight={undefined} isEmpty={false} diff --git a/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx b/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx index 45ae7c7111c51f..2bd7527e433ddf 100644 --- a/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx +++ b/packages/kbn-unified-field-list/src/components/field_item_button/field_item_button.tsx @@ -16,6 +16,8 @@ import { FieldIcon, getFieldIconProps, getFieldSearchMatchingHighlight } from '@ import { type FieldListItem, type GetCustomFieldType } from '../../types'; import './field_item_button.scss'; +const DRAG_ICON = ; + /** * Props of FieldItemButton component */ @@ -28,7 +30,7 @@ export interface FieldItemButtonProps { infoIcon?: FieldButtonProps['fieldInfoIcon']; className?: FieldButtonProps['className']; flush?: FieldButtonProps['flush']; - dragHandle?: FieldButtonProps['dragHandle']; + withDragIcon?: boolean; getCustomFieldType?: GetCustomFieldType; dataTestSubj?: string; size?: FieldButtonProps['size']; @@ -52,6 +54,7 @@ export interface FieldItemButtonProps { * @param getCustomFieldType * @param dataTestSubj * @param size + * @param withDragIcon * @param onClick * @param shouldAlwaysShowAction * @param buttonAddFieldToWorkspaceProps @@ -73,6 +76,7 @@ export function FieldItemButton({ getCustomFieldType, dataTestSubj, size, + withDragIcon, onClick, shouldAlwaysShowAction, buttonAddFieldToWorkspaceProps, @@ -104,7 +108,7 @@ export function FieldItemButton({ [`unifiedFieldListItemButton--${type}`]: type, [`unifiedFieldListItemButton--exists`]: !isEmpty, [`unifiedFieldListItemButton--missing`]: isEmpty, - [`unifiedFieldListItemButton--withDragHandle`]: Boolean(otherProps.dragHandle), + [`unifiedFieldListItemButton--withDragIcon`]: Boolean(withDragIcon), }, className ); @@ -196,7 +200,16 @@ export function FieldItemButton({ }, }), }} - fieldIcon={} + fieldIcon={ +
+
+ +
+ {withDragIcon && ( +
{DRAG_ICON}
+ )} +
+ } fieldName={ Date: Wed, 3 Jan 2024 16:28:34 +0100 Subject: [PATCH 05/18] [UnifiedSearch] Expose QueryStringInput via plugin contract (#173810) ## Summary Expose QueryStringInput via plugin contract this will make sure deps are handled by the component itself. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../unified_search/public/mocks/mocks.ts | 1 + src/plugins/unified_search/public/plugin.ts | 13 +++++ .../get_query_string_input.tsx | 16 +++++ src/plugins/unified_search/public/types.ts | 3 +- src/plugins/vis_default_editor/kibana.jsonc | 4 +- .../public/components/controls/filter.tsx | 23 +------- .../vis_default_editor/public/types.ts | 6 +- .../trace_explorer/trace_search_box/index.tsx | 21 ++----- .../components/agent_logs/query_bar.tsx | 10 ++-- x-pack/plugins/graph/kibana.jsonc | 1 - .../public/components/search_bar.test.tsx | 58 +++++++++++-------- .../graph/public/components/search_bar.tsx | 34 +++++------ x-pack/plugins/ml/kibana.jsonc | 3 +- .../exploration_query_bar.tsx | 22 ++----- .../explorer_query_bar/explorer_query_bar.tsx | 22 +------ .../components/common/query_builder.tsx | 18 ++---- .../public/pages/slo_edit/slo_edit.test.tsx | 3 + .../slos/components/slo_list_search_bar.tsx | 18 ++---- .../public/pages/slos/slos.test.tsx | 3 + .../geo_containment/rule_form/query_input.tsx | 24 +------- .../components/alerts/query_bar.tsx | 23 +------- x-pack/plugins/transform/kibana.jsonc | 1 - .../public/app/__mocks__/app_dependencies.tsx | 4 +- .../source_search_bar/source_search_bar.tsx | 28 +-------- .../step_define/step_define_form.test.tsx | 2 + x-pack/plugins/uptime/kibana.jsonc | 1 - .../public/legacy_uptime/app/uptime_app.tsx | 1 + .../alerts/alert_query_bar/query_bar.tsx | 24 +------- .../overview/query_bar/query_bar.tsx | 24 +------- .../legacy_uptime/pages/overview.test.tsx | 5 +- 30 files changed, 144 insertions(+), 272 deletions(-) create mode 100644 src/plugins/unified_search/public/query_string_input/get_query_string_input.tsx diff --git a/src/plugins/unified_search/public/mocks/mocks.ts b/src/plugins/unified_search/public/mocks/mocks.ts index 3c2ea2a0a38fe7..f5a10733203215 100644 --- a/src/plugins/unified_search/public/mocks/mocks.ts +++ b/src/plugins/unified_search/public/mocks/mocks.ts @@ -38,6 +38,7 @@ const createStartContract = (): Start => { SearchBar: jest.fn().mockReturnValue(null), AggregateQuerySearchBar: jest.fn().mockReturnValue(null), FiltersBuilderLazy: jest.fn(), + QueryStringInput: jest.fn().mockReturnValue('QueryStringInput'), }, }; }; diff --git a/src/plugins/unified_search/public/plugin.ts b/src/plugins/unified_search/public/plugin.ts index aa86d433547aff..7644ff8646c10a 100755 --- a/src/plugins/unified_search/public/plugin.ts +++ b/src/plugins/unified_search/public/plugin.ts @@ -9,6 +9,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/cor import { Storage, IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; +import { createQueryStringInput } from './query_string_input/get_query_string_input'; import { UPDATE_FILTER_REFERENCES_TRIGGER, updateFilterReferencesTrigger } from './triggers'; import { ConfigSchema } from '../config'; import { setIndexPatterns, setTheme, setOverlays } from './services'; @@ -108,6 +109,18 @@ export class UnifiedSearchPublicPlugin getCustomSearchBar, AggregateQuerySearchBar: SearchBar, FiltersBuilderLazy, + QueryStringInput: createQueryStringInput({ + data, + dataViews, + docLinks: core.docLinks, + http: core.http, + notifications: core.notifications, + storage: this.storage, + uiSettings: core.uiSettings, + unifiedSearch: { + autocomplete: autocompleteStart, + }, + }), }, autocomplete: autocompleteStart, }; diff --git a/src/plugins/unified_search/public/query_string_input/get_query_string_input.tsx b/src/plugins/unified_search/public/query_string_input/get_query_string_input.tsx new file mode 100644 index 00000000000000..7581bc74720648 --- /dev/null +++ b/src/plugins/unified_search/public/query_string_input/get_query_string_input.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryStringInput, QueryStringInputProps } from '.'; + +export function createQueryStringInput(deps: QueryStringInputProps['deps']) { + return (props: Omit) => { + return ; + }; +} diff --git a/src/plugins/unified_search/public/types.ts b/src/plugins/unified_search/public/types.ts index a6360b69cca260..73c581e8f4c274 100755 --- a/src/plugins/unified_search/public/types.ts +++ b/src/plugins/unified_search/public/types.ts @@ -18,7 +18,7 @@ import { CoreStart, DocLinksStart } from '@kbn/core/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; -import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; +import type { IndexPatternSelectProps, QueryStringInputProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; import { StatefulSearchBarDeps } from './search_bar/create_search_bar'; @@ -53,6 +53,7 @@ export interface UnifiedSearchPublicPluginStartUi { SearchBar: (props: StatefulSearchBarProps) => React.ReactElement; AggregateQuerySearchBar: AggQuerySearchBarComp; FiltersBuilderLazy: React.ComponentType; + QueryStringInput: React.ComponentType>; } /** diff --git a/src/plugins/vis_default_editor/kibana.jsonc b/src/plugins/vis_default_editor/kibana.jsonc index 4487cce5d440f0..15db2338f2ca7d 100644 --- a/src/plugins/vis_default_editor/kibana.jsonc +++ b/src/plugins/vis_default_editor/kibana.jsonc @@ -8,13 +8,13 @@ "server": false, "browser": true, "requiredPlugins": [ - "dataViews" + "dataViews", + "unifiedSearch", ], "optionalPlugins": [ "visualizations" ], "requiredBundles": [ - "unifiedSearch", "kibanaUtils", "kibanaReact", "data", diff --git a/src/plugins/vis_default_editor/public/components/controls/filter.tsx b/src/plugins/vis_default_editor/public/components/controls/filter.tsx index 18ad8a3f33d280..ab05f9a2d34ede 100644 --- a/src/plugins/vis_default_editor/public/components/controls/filter.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filter.tsx @@ -12,7 +12,6 @@ import { i18n } from '@kbn/i18n'; import type { Query } from '@kbn/es-query'; import { IAggConfig } from '@kbn/data-plugin/public'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -45,14 +44,9 @@ function FilterRow({ const { services } = useKibana(); const { data, - dataViews, - unifiedSearch, - usageCollection, - storage, - notifications, - http, - docLinks, - uiSettings, + unifiedSearch: { + ui: { QueryStringInput }, + }, appName, } = services; @@ -117,17 +111,6 @@ function FilterRow({ bubbleSubmitEvent={true} languageSwitcherPopoverAnchorPosition="leftDown" size="s" - deps={{ - data, - dataViews, - unifiedSearch, - usageCollection, - storage, - notifications, - http, - docLinks, - uiSettings, - }} appName={appName} />
diff --git a/src/plugins/vis_default_editor/public/types.ts b/src/plugins/vis_default_editor/public/types.ts index 2cef7977cd0112..2d5077dbba3357 100644 --- a/src/plugins/vis_default_editor/public/types.ts +++ b/src/plugins/vis_default_editor/public/types.ts @@ -9,7 +9,7 @@ import type { CoreStart, DocLinksStart } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; @@ -17,9 +17,7 @@ export interface VisDefaultEditorKibanaServices { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; appName: string; - unifiedSearch: { - autocomplete: AutocompleteStart; - }; + unifiedSearch: UnifiedSearchPublicPluginStart; usageCollection?: UsageCollectionStart; storage: IStorageWrapper; notifications: CoreStart['notifications']; diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx index 6c83b02456b1c1..0995f8555ae29a 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx @@ -13,9 +13,6 @@ import { EuiSelectOption, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { ApmPluginStartDeps } from '../../../../plugin'; import { TraceSearchQuery, TraceSearchType, @@ -54,11 +51,11 @@ export function TraceSearchBox({ onQueryCommit, loading, }: Props) { - const { unifiedSearch, core, data, dataViews } = useApmPluginContext(); - const { notifications, http, docLinks, uiSettings } = core; const { - services: { storage }, - } = useKibana(); + unifiedSearch: { + ui: { QueryStringInput }, + }, + } = useApmPluginContext(); const { dataView } = useApmDataView(); @@ -106,16 +103,6 @@ export function TraceSearchBox({ defaultMessage: 'APM', } )} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - storage, - }} /> )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx index 684a00eb59e320..d21243732f66fa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx @@ -9,7 +9,6 @@ import React, { memo, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useStartServices } from '../../../../../hooks'; @@ -28,8 +27,12 @@ export const LogQueryBar: React.FunctionComponent<{ isQueryValid: boolean; onUpdateQuery: (query: string, runQuery?: boolean) => void; }> = memo(({ query, isQueryValid, onUpdateQuery }) => { - const { data, notifications, http, docLinks, uiSettings, unifiedSearch, storage, dataViews } = - useStartServices(); + const { + data, + unifiedSearch: { + ui: { QueryStringInput }, + }, + } = useStartServices(); const [indexPatternFields, setIndexPatternFields] = useState(); useEffect(() => { @@ -81,7 +84,6 @@ export const LogQueryBar: React.FunctionComponent<{ onUpdateQuery(newQuery.query as string, true); }} appName={i18n.translate('xpack.fleet.appTitle', { defaultMessage: 'Fleet' })} - deps={{ unifiedSearch, notifications, http, docLinks, uiSettings, data, dataViews, storage }} /> ); }); diff --git a/x-pack/plugins/graph/kibana.jsonc b/x-pack/plugins/graph/kibana.jsonc index b75a0689bc2921..7e6093df5813cf 100644 --- a/x-pack/plugins/graph/kibana.jsonc +++ b/x-pack/plugins/graph/kibana.jsonc @@ -27,7 +27,6 @@ "spaces" ], "requiredBundles": [ - "unifiedSearch", "kibanaUtils", "kibanaReact" ] diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index e1ee8cb9d6331c..16e5b5f0651aa4 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -30,41 +30,51 @@ import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import { createQueryStringInput } from '@kbn/unified-search-plugin/public/query_string_input/get_query_string_input'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); const waitForIndexPatternFetch = () => new Promise((r) => setTimeout(r)); function getServiceMocks() { - return { - uiSettings: { - get: (key: string) => { - return 10; + const docLinks = { + links: { + query: { + kueryQuerySyntax: '', }, - } as IUiSettingsClient, + }, + } as DocLinksStart; + const uiSettings = { + get: (key: string) => { + return 10; + }, + } as IUiSettingsClient; + + return { savedObjects: {} as SavedObjectsStart, savedObjectsManagement: {} as SavedObjectsManagementPluginStart, - notifications: {} as NotificationsStart, - docLinks: { - links: { - query: { - kueryQuerySyntax: '', - }, - }, - } as DocLinksStart, - http: {} as HttpStart, overlays: {} as OverlayStart, - storage: { - get: () => {}, - }, - data: { - query: { - savedQueries: {}, - }, - }, unifiedSearch: { - autocomplete: { - hasQuerySuggestions: () => false, + ui: { + QueryStringInput: createQueryStringInput({ + docLinks, + uiSettings, + storage: { + get: () => {}, + set: () => {}, + remove: () => {}, + clear: () => {}, + } as IStorageWrapper, + data: dataPluginMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), + notifications: {} as NotificationsStart, + http: {} as HttpStart, + dataViews: dataViewPluginMocks.createStartContract(), + }), }, }, }; diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index ad604ddc590914..219483c65656bb 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -7,16 +7,18 @@ import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiToolTip } from '@elastic/eui'; import React, { useState, useEffect } from 'react'; - import { i18n } from '@kbn/i18n'; import { connect } from 'react-redux'; import { toElasticsearchQuery, fromKueryExpression, Query } from '@kbn/es-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { TooltipWrapper } from '@kbn/visualization-utils'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { IUnifiedSearchPluginServices } from '@kbn/unified-search-plugin/public/types'; +import { + IUnifiedSearchPluginServices, + UnifiedSearchPublicPluginStart, +} from '@kbn/unified-search-plugin/public/types'; import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; + import { IndexPatternSavedObject, IndexPatternProvider, WorkspaceField } from '../types'; import { openSourceModal } from '../services/source_modal'; import { @@ -96,19 +98,19 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) }, [currentDatasource, indexPatternProvider, onIndexPatternChange]); const kibana = useKibana< - IUnifiedSearchPluginServices & { contentManagement: ContentManagementPublicStart } + IUnifiedSearchPluginServices & { + contentManagement: ContentManagementPublicStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + } >(); + const { services, overlays } = kibana; const { uiSettings, appName, - unifiedSearch, - data, - dataViews, - storage, - notifications, - http, - docLinks, + unifiedSearch: { + ui: { QueryStringInput }, + }, contentManagement, } = services; if (!overlays) return null; @@ -178,16 +180,6 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) query={query} onChange={setQuery} appName={appName} - deps={{ - unifiedSearch, - data, - dataViews, - storage, - notifications, - http, - docLinks, - uiSettings, - }} />
diff --git a/x-pack/plugins/ml/kibana.jsonc b/x-pack/plugins/ml/kibana.jsonc index e3afdf35d0c4fc..e2e4e5965d673d 100644 --- a/x-pack/plugins/ml/kibana.jsonc +++ b/x-pack/plugins/ml/kibana.jsonc @@ -55,8 +55,7 @@ "lens", "maps", "savedObjectsFinder", - "usageCollection", - "unifiedSearch" + "usageCollection" ], "extraPublicDirs": ["common"] } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index e1efd592e956e6..a9581d44c21ebd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -7,18 +7,15 @@ import React, { FC, useEffect, useMemo, useState } from 'react'; import { debounce } from 'lodash'; - import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { EuiButtonGroup, EuiCode, EuiFlexGroup, EuiFlexItem, EuiInputPopover } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; import { fromKueryExpression, luceneStringToDsl, toElasticsearchQuery } from '@kbn/es-query'; import { DataView } from '@kbn/data-views-plugin/common'; import type { Query } from '@kbn/es-query'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { QueryErrorMessage } from '@kbn/ml-error-utils'; - import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '@kbn/ml-query-utils'; + import { PLUGIN_ID } from '../../../../../../../common/constants/app'; import { Dictionary } from '../../../../../../../common/types/common'; import { removeFilterFromQueryString } from '../../../../../explorer/explorer_utils'; @@ -54,8 +51,11 @@ export const ExplorationQueryBar: FC = ({ ); const { services } = useMlKibana(); - const { unifiedSearch, data, storage, notifications, http, docLinks, uiSettings, dataViews } = - services; + const { + unifiedSearch: { + ui: { QueryStringInput }, + }, + } = services; const searchChangeHandler = (q: Query) => setSearchInput(q); @@ -199,16 +199,6 @@ export const ExplorationQueryBar: FC = ({ dataTestSubj="mlDFAnalyticsQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" appName={PLUGIN_ID} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - storage, - dataViews, - }} /> {filters && filters.options && ( diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index 9e55ade9f6bd58..746e3c3beb79fe 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -10,7 +10,6 @@ import { EuiCode, EuiInputPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { fromKueryExpression, luceneStringToDsl, toElasticsearchQuery } from '@kbn/es-query'; import type { Query } from '@kbn/es-query'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { QueryErrorMessage } from '@kbn/ml-error-utils'; import type { InfluencersFilterQuery } from '@kbn/ml-anomaly-utils'; @@ -111,14 +110,9 @@ export const ExplorerQueryBar: FC = ({ const { anomalyExplorerCommonStateService } = useAnomalyExplorerContext(); const { services } = useMlKibana(); const { - unifiedSearch, - data, - storage, - notifications, - http, - docLinks, - uiSettings, - dataViews: dataViewsService, + unifiedSearch: { + ui: { QueryStringInput }, + }, } = services; // The internal state of the input query bar updated on every key stroke. @@ -177,16 +171,6 @@ export const ExplorerQueryBar: FC = ({ dataTestSubj="explorerQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" appName={PLUGIN_ID} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - storage, - dataViews: dataViewsService, - }} /> } isOpen={queryErrorMessage?.query === searchInput.query && queryErrorMessage?.message !== ''} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx index ab60a66daa2fde..db451234b6e317 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/common/query_builder.tsx @@ -6,7 +6,6 @@ */ import { EuiFormRow } from '@elastic/eui'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import React, { ReactNode } from 'react'; import { Controller, FieldPath, useFormContext } from 'react-hook-form'; import { useCreateDataView } from '../../../../hooks/use_create_data_view'; @@ -32,8 +31,11 @@ export function QueryBuilder({ required, tooltip, }: Props) { - const { data, docLinks, dataViews, http, notifications, storage, uiSettings, unifiedSearch } = - useKibana().services; + const { + unifiedSearch: { + ui: { QueryStringInput }, + }, + } = useKibana().services; const { control, getFieldState } = useFormContext(); @@ -67,16 +69,6 @@ export function QueryBuilder({ appName="Observability" bubbleSubmitEvent={false} dataTestSubj={dataTestSubj} - deps={{ - data, - dataViews, - docLinks, - http, - notifications, - storage, - uiSettings, - unifiedSearch, - }} disableAutoFocus disableLanguageSwitcher indexPatterns={dataView ? [dataView] : []} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx index 3eca039382b354..c27966369fee2c 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx @@ -119,6 +119,9 @@ const mockKibana = () => { get: () => {}, }, unifiedSearch: { + ui: { + QueryStringInput: () =>
Query String Input
, + }, autocomplete: { hasQuerySuggestions: () => {}, }, diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx index b06a2f3fea4784..c1f08ca69fd9d8 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx @@ -18,7 +18,6 @@ import { import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option'; import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import React, { useState } from 'react'; import { useCreateDataView } from '../../../hooks/use_create_data_view'; import { useKibana } from '../../../utils/kibana_react'; @@ -70,8 +69,11 @@ const SORT_OPTIONS: Array> = [ export type ViewMode = 'default' | 'compact'; export function SloListSearchBar({ loading, onChangeQuery, onChangeSort, initialState }: Props) { - const { data, dataViews, docLinks, http, notifications, storage, uiSettings, unifiedSearch } = - useKibana().services; + const { + unifiedSearch: { + ui: { QueryStringInput }, + }, + } = useKibana().services; const { dataView } = useCreateDataView({ indexPatternString: '.slo-observability.summary-*' }); const [query, setQuery] = useState(initialState.kqlQuery); @@ -97,16 +99,6 @@ export function SloListSearchBar({ loading, onChangeQuery, onChangeSort, initial { setQuery(String(value.query)); diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index 61e6190b799566..430a1605150394 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -110,6 +110,9 @@ const mockKibana = () => { }, }, unifiedSearch: { + ui: { + QueryStringInput: () =>
Query String Input
, + }, autocomplete: { hasQuerySuggestions: () => {}, }, diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx index 5a578276dbdd02..b87047eece035f 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx @@ -6,7 +6,6 @@ */ import React, { useState } from 'react'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { Query } from '@kbn/es-query'; @@ -39,15 +38,9 @@ interface Props { export const QueryInput = (props: Props) => { const { - data, - dataViews, - docLinks, - http, - notifications, - storage, - uiSettings, - unifiedSearch, - usageCollection, + unifiedSearch: { + ui: { QueryStringInput }, + }, } = useKibana<{ data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; @@ -82,17 +75,6 @@ export const QueryInput = (props: Props) => { } }} appName={STACK_ALERTS_FEATURE_ID} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - storage, - usageCollection, - }} /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx index dd9aada9b8d206..b2192731b4ab9b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx @@ -8,7 +8,6 @@ import React, { useEffect, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useFetcher } from '@kbn/observability-shared-plugin/public'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../common/constants'; @@ -38,15 +37,10 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { const { appName, - notifications, - http, - docLinks, - uiSettings, - data, dataViews, - unifiedSearch, - storage, - usageCollection, + unifiedSearch: { + ui: { QueryStringInput }, + }, } = services; const [inputVal, setInputVal] = useState(query); @@ -85,17 +79,6 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { defaultMessage: 'Filter using kql syntax', })} appName={appName} - deps={{ - unifiedSearch, - data, - dataViews, - storage, - notifications, - http, - docLinks, - uiSettings, - usageCollection, - }} /> ); diff --git a/x-pack/plugins/transform/kibana.jsonc b/x-pack/plugins/transform/kibana.jsonc index ef3577846238ac..499206b4db9248 100644 --- a/x-pack/plugins/transform/kibana.jsonc +++ b/x-pack/plugins/transform/kibana.jsonc @@ -36,7 +36,6 @@ "alerting" ], "requiredBundles": [ - "unifiedSearch", "esUiShared", "discover", "kibanaUtils", diff --git a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx index 0e2dd130fa94f1..1016d4fd8ecbad 100644 --- a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx @@ -23,7 +23,6 @@ import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks' import { SharePluginStart } from '@kbn/share-plugin/public'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { savedSearchPluginMock } from '@kbn/saved-search-plugin/public/mocks'; import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks'; @@ -32,6 +31,7 @@ import { MlSharedContext } from './shared_context'; import type { GetMlSharedImportsReturnType } from '../../shared_imports'; import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { settingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); @@ -93,7 +93,7 @@ const appDependencies: AppDependencies = { share: { urlGenerators: { getUrlGenerator: jest.fn() } } as unknown as SharePluginStart, ml: {} as GetMlSharedImportsReturnType, triggersActionsUi: {} as jest.Mocked, - unifiedSearch: {} as jest.Mocked, + unifiedSearch: unifiedSearchPluginMock.createStartContract(), savedObjectsManagement: {} as jest.Mocked, settings: settingsServiceMock.createStartContract(), savedSearch: savedSearchPluginMock.createStartContract(), diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx index 42b9d556f9dc27..90bee6274e930d 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx @@ -6,16 +6,11 @@ */ import React, { type FC } from 'react'; - import { EuiCode, EuiInputPopover } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; - import { PLUGIN } from '../../../../../../common/constants'; import { SearchItems } from '../../../../hooks/use_search_items'; - import { StepDefineFormHook, QUERY_LANGUAGE_KUERY } from '../step_define'; import { useAppDependencies } from '../../../../app_dependencies'; @@ -30,15 +25,9 @@ export const SourceSearchBar: FC = ({ dataView, searchBar } = searchBar; const { - uiSettings, - notifications, - http, - docLinks, - data, - dataViews, - storage, - unifiedSearch, - usageCollection, + unifiedSearch: { + ui: { QueryStringInput }, + }, } = useAppDependencies(); return ( @@ -67,17 +56,6 @@ export const SourceSearchBar: FC = ({ dataView, searchBar dataTestSubj="transformQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" appName={PLUGIN.getI18nName()} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - storage, - usageCollection, - }} /> } isOpen={queryErrorMessage?.query === searchInput.query && queryErrorMessage?.message !== ''} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 3470cf5706a2e9..325ebb931d513a 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -30,6 +30,7 @@ import { StepDefineForm } from './step_define_form'; import { MlSharedContext } from '../../../../__mocks__/shared_context'; import { getMlSharedImports } from '../../../../../shared_imports'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); @@ -81,6 +82,7 @@ describe('Transform: ', () => { const services = { ...startMock, data: dataPluginMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), appName: 'the-test-app', storage: createMockStorage(), }; diff --git a/x-pack/plugins/uptime/kibana.jsonc b/x-pack/plugins/uptime/kibana.jsonc index c07d0dc342a740..7890603e4c3d50 100644 --- a/x-pack/plugins/uptime/kibana.jsonc +++ b/x-pack/plugins/uptime/kibana.jsonc @@ -36,7 +36,6 @@ ], "optionalPlugins": ["cloud", "data", "fleet", "home", "ml", "spaces", "telemetry"], "requiredBundles": [ - "unifiedSearch", "fleet", "kibanaReact", "kibanaUtils", diff --git a/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx b/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx index 0dd752ab2ff649..c82ac50a72594d 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx @@ -117,6 +117,7 @@ const Application = (props: UptimeAppProps) => { ...plugins, storage, data: startPlugins.data, + unifiedSearch: startPlugins.unifiedSearch, fleet: startPlugins.fleet, inspector: startPlugins.inspector, triggersActionsUi: startPlugins.triggersActionsUi, diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx index 8692a8a01f0da7..4919f2899f85f6 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -8,7 +8,6 @@ import React, { useEffect, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { isValidKuery } from '../../query_bar/query_bar'; import * as labels from '../translations'; @@ -26,15 +25,9 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { const { appName, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - unifiedSearch, - storage, - usageCollection, + unifiedSearch: { + ui: { QueryStringInput }, + }, } = services; const [inputVal, setInputVal] = useState(query); @@ -70,17 +63,6 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { defaultMessage: 'Filter using kql syntax', })} appName={appName} - deps={{ - unifiedSearch, - data, - dataViews, - storage, - notifications, - http, - docLinks, - uiSettings, - usageCollection, - }} /> ); diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx index 8f3f88bd039fed..cf3ec6c4540fd2 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx @@ -8,7 +8,6 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem } from '@elastic/eui'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SyntaxType, useQueryBar } from './use_query_bar'; import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations'; @@ -38,15 +37,9 @@ export const QueryBar = () => { const { appName, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - unifiedSearch, - storage, - usageCollection, + unifiedSearch: { + ui: { QueryStringInput }, + }, } = services; const { query, setQuery, submitImmediately } = useQueryBar(); @@ -94,17 +87,6 @@ export const QueryBar = () => { } isInvalid={isInValid()} appName={appName} - deps={{ - unifiedSearch, - notifications, - http, - docLinks, - uiSettings, - data, - dataViews, - storage, - usageCollection, - }} /> ); diff --git a/x-pack/plugins/uptime/public/legacy_uptime/pages/overview.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/pages/overview.test.tsx index 0005e2bb6b0909..57ca69c0de1842 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/pages/overview.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/pages/overview.test.tsx @@ -8,14 +8,13 @@ import React from 'react'; import { OverviewPageComponent } from './overview'; import { render } from '../lib/helper/rtl_helpers'; -import { SIMPLE_SEARCH_PLACEHOLDER } from '../components/overview/query_bar/translations'; describe('MonitorPage', () => { it('renders expected elements for valid props', async () => { - const { findByText, findByPlaceholderText } = render(); + const { findByText } = render(); expect(await findByText('No uptime monitors found')).toBeInTheDocument(); - expect(await findByPlaceholderText(SIMPLE_SEARCH_PLACEHOLDER)).toBeInTheDocument(); + expect(await findByText('QueryStringInput')).toBeInTheDocument(); }); }); From cb641a0b07952a4357ac57f145df822e4ba285c3 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 3 Jan 2024 09:29:55 -0600 Subject: [PATCH 06/18] [artifacts] Publish ubi9 docker image (#170264) --- .buildkite/scripts/steps/artifacts/publish.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildkite/scripts/steps/artifacts/publish.sh b/.buildkite/scripts/steps/artifacts/publish.sh index 162e552d48cd40..2621242fe0aa7e 100644 --- a/.buildkite/scripts/steps/artifacts/publish.sh +++ b/.buildkite/scripts/steps/artifacts/publish.sh @@ -20,7 +20,7 @@ download "kibana-$FULL_VERSION-docker-image.tar.gz" download "kibana-$FULL_VERSION-docker-image-aarch64.tar.gz" download "kibana-cloud-$FULL_VERSION-docker-image.tar.gz" download "kibana-cloud-$FULL_VERSION-docker-image-aarch64.tar.gz" -download "kibana-ubi8-$FULL_VERSION-docker-image.tar.gz" +download "kibana-ubi-$FULL_VERSION-docker-image.tar.gz" download "kibana-$FULL_VERSION-arm64.deb" download "kibana-$FULL_VERSION-amd64.deb" @@ -30,7 +30,7 @@ download "kibana-$FULL_VERSION-aarch64.rpm" download "kibana-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-cloud-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-ironbank-$FULL_VERSION-docker-build-context.tar.gz" -download "kibana-ubi8-$FULL_VERSION-docker-build-context.tar.gz" +download "kibana-ubi-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-$FULL_VERSION-linux-aarch64.tar.gz" download "kibana-$FULL_VERSION-linux-x86_64.tar.gz" From 9d66265931f92edf98fa77daf0e673404f365405 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 3 Jan 2024 08:42:52 -0700 Subject: [PATCH 07/18] [maps] ES|QL source (#173481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/167648 PR adds "ES|QL" card to "Add Layer" interface. Creates a layer renders an ES|QL statement on the map Screenshot 2023-12-16 at 2 03 04 PM Screenshot 2023-12-16 at 1 54 24 PM ### Known limitations This PR is intended to be a first start and does not cover all functionality. The following list identifies known limitations that will have to be resolved in future work. 1. tooltips - Existing documents source supports lazy loading tooltips to avoid pulling unused data on map render. How would this look for ES|QL? Should tooltips only support local data? 2. ES|QL layer does not surface data view to unified search bar so search type-ahead and filter bar will not show index-pattern fields from ES|QL layer. 3. ES|QL layer does not surface geoField. This affects control for drawing filters on map. 4. ES|QL layer does not support pulling field meta from Elasticsearch. Instead, data-driven styling uses ranges from local data set. This will be tricky as we can't just pull field ranges from index-pattern. Also need to account for WHERE clause and other edge cases. 5. fit to bounds --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Nick Peihl --- package.json | 1 + packages/kbn-es-query/index.ts | 1 + .../src/es_query/es_aggregate_query.test.ts | 28 ++ .../src/es_query/es_aggregate_query.ts | 14 + packages/kbn-es-query/src/es_query/index.ts | 1 + packages/kbn-es-types/index.ts | 3 + packages/kbn-es-types/src/index.ts | 6 + packages/kbn-es-types/src/search.ts | 12 + .../src/editor_footer.tsx | 3 + .../src/text_based_languages_editor.tsx | 9 +- .../data/common/search/expressions/esql.ts | 9 +- .../strategies/es_search/elasticsearch.ts | 6 - src/plugins/data/tsconfig.json | 3 +- x-pack/plugins/maps/common/constants.ts | 2 + .../source_descriptor_types.ts | 15 + .../common/telemetry/layer_stats_collector.ts | 4 + x-pack/plugins/maps/common/telemetry/types.ts | 1 + x-pack/plugins/maps/kibana.jsonc | 3 +- .../maps/public/actions/layer_actions.ts | 15 +- .../layers/build_vector_request_meta.ts | 2 +- .../raster_tile_layer/raster_tile_layer.ts | 2 +- .../layers/wizards/layer_wizard_registry.ts | 1 + .../layers/wizards/load_layer_wizards.ts | 12 +- .../es_geo_line_source/es_geo_line_source.tsx | 18 +- .../es_search_source/es_search_source.tsx | 10 +- .../classes/sources/es_source/es_source.ts | 4 + .../esql_source/convert_to_geojson.test.ts | 49 +++ .../sources/esql_source/convert_to_geojson.ts | 47 +++ .../esql_source/create_source_editor.tsx | 119 +++++++ .../sources/esql_source/esql_editor.tsx | 106 +++++++ .../sources/esql_source/esql_layer_wizard.tsx | 47 +++ .../sources/esql_source/esql_source.tsx | 292 ++++++++++++++++++ .../classes/sources/esql_source/esql_utils.ts | 130 ++++++++ .../classes/sources/esql_source/index.ts | 9 + .../esql_source/update_source_editor.tsx | 205 ++++++++++++ .../mvt_single_layer_vector_source.tsx | 4 + .../public/classes/sources/setup_sources.ts | 6 + .../maps/public/classes/sources/source.ts | 3 + .../sources/vector_source/vector_source.tsx | 11 +- .../components/force_refresh_checkbox.tsx | 4 +- .../flyout_body/flyout_body.tsx | 1 + .../add_layer_panel/flyout_body/index.ts | 3 +- .../flyout_body/layer_wizard_select.tsx | 8 +- .../maps/public/selectors/map_selectors.ts | 36 +++ .../maps_telemetry/collectors/register.ts | 18 ++ x-pack/plugins/maps/tsconfig.json | 3 + .../plugins/ml/public/maps/anomaly_source.tsx | 4 + .../schema/xpack_plugins.json | 28 ++ .../apis/maps/maps_telemetry.ts | 49 +-- .../apps/maps/group1/esql_source.ts | 34 ++ .../test/functional/apps/maps/group1/index.js | 1 + .../fixtures/kbn_archiver/maps.json | 22 ++ yarn.lock | 38 ++- 53 files changed, 1398 insertions(+), 64 deletions(-) create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.test.ts create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/create_source_editor.tsx create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/esql_layer_wizard.tsx create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/index.ts create mode 100644 x-pack/plugins/maps/public/classes/sources/esql_source/update_source_editor.tsx create mode 100644 x-pack/test/functional/apps/maps/group1/esql_source.ts diff --git a/package.json b/package.json index 68122ca59fc5bb..eb171c92f1b5c5 100644 --- a/package.json +++ b/package.json @@ -1111,6 +1111,7 @@ "vega-spec-injector": "^0.0.2", "vega-tooltip": "^0.28.0", "vinyl": "^2.2.0", + "wellknown": "^0.5.0", "whatwg-fetch": "^3.0.0", "xml2js": "^0.5.0", "xstate": "^4.38.2", diff --git a/packages/kbn-es-query/index.ts b/packages/kbn-es-query/index.ts index cfcf8239196df8..51cbebb5470349 100644 --- a/packages/kbn-es-query/index.ts +++ b/packages/kbn-es-query/index.ts @@ -55,6 +55,7 @@ export { getAggregateQueryMode, getIndexPatternFromSQLQuery, getIndexPatternFromESQLQuery, + getLimitFromESQLQuery, getLanguageDisplayName, cleanupESQLQueryForLensSuggestions, } from './src/es_query'; diff --git a/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts b/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts index 504e6a5c93d442..f223d3964be246 100644 --- a/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts +++ b/packages/kbn-es-query/src/es_query/es_aggregate_query.test.ts @@ -12,6 +12,7 @@ import { getAggregateQueryMode, getIndexPatternFromSQLQuery, getIndexPatternFromESQLQuery, + getLimitFromESQLQuery, cleanupESQLQueryForLensSuggestions, } from './es_aggregate_query'; @@ -117,6 +118,33 @@ describe('sql query helpers', () => { }); }); + describe('getLimitFromESQLQuery', () => { + it('should return default limit when ES|QL query is empty', () => { + const limit = getLimitFromESQLQuery(''); + expect(limit).toBe(500); + }); + + it('should return default limit when ES|QL query does not contain LIMIT command', () => { + const limit = getLimitFromESQLQuery('FROM foo'); + expect(limit).toBe(500); + }); + + it('should return default limit when ES|QL query contains invalid LIMIT command', () => { + const limit = getLimitFromESQLQuery('FROM foo | LIMIT iAmNotANumber'); + expect(limit).toBe(500); + }); + + it('should return limit when ES|QL query contains LIMIT command', () => { + const limit = getLimitFromESQLQuery('FROM foo | LIMIT 10000 | KEEP myField'); + expect(limit).toBe(10000); + }); + + it('should return last limit when ES|QL query contains multiple LIMIT command', () => { + const limit = getLimitFromESQLQuery('FROM foo | LIMIT 200 | LIMIT 0'); + expect(limit).toBe(0); + }); + }); + describe('cleanupESQLQueryForLensSuggestions', () => { it('should not remove anything if a drop command is not present', () => { expect(cleanupESQLQueryForLensSuggestions('from a | eval b = 1')).toBe('from a | eval b = 1'); diff --git a/packages/kbn-es-query/src/es_query/es_aggregate_query.ts b/packages/kbn-es-query/src/es_query/es_aggregate_query.ts index f7465058963606..ea39ee4ef749e3 100644 --- a/packages/kbn-es-query/src/es_query/es_aggregate_query.ts +++ b/packages/kbn-es-query/src/es_query/es_aggregate_query.ts @@ -5,10 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import type { Query, AggregateQuery } from '../filters'; type Language = keyof AggregateQuery; +const DEFAULT_ESQL_LIMIT = 500; + // Checks if the query is of type Query export function isOfQueryType(arg?: Query | AggregateQuery): arg is Query { return Boolean(arg && 'query' in arg); @@ -67,6 +70,17 @@ export function getIndexPatternFromESQLQuery(esql?: string): string { return ''; } +export function getLimitFromESQLQuery(esql: string): number { + const limitCommands = esql.match(new RegExp(/LIMIT\s[0-9]+/, 'ig')); + if (!limitCommands) { + return DEFAULT_ESQL_LIMIT; + } + + const lastIndex = limitCommands.length - 1; + const split = limitCommands[lastIndex].split(' '); + return parseInt(split[1], 10); +} + export function cleanupESQLQueryForLensSuggestions(esql?: string): string { const pipes = (esql || '').split('|'); return pipes.filter((statement) => !/DROP\s/i.test(statement)).join('|'); diff --git a/packages/kbn-es-query/src/es_query/index.ts b/packages/kbn-es-query/src/es_query/index.ts index 18009145a432f7..71e8078b7bfaba 100644 --- a/packages/kbn-es-query/src/es_query/index.ts +++ b/packages/kbn-es-query/src/es_query/index.ts @@ -20,6 +20,7 @@ export { getIndexPatternFromSQLQuery, getLanguageDisplayName, getIndexPatternFromESQLQuery, + getLimitFromESQLQuery, cleanupESQLQueryForLensSuggestions, } from './es_aggregate_query'; export { fromCombinedFilter } from './from_combined_filter'; diff --git a/packages/kbn-es-types/index.ts b/packages/kbn-es-types/index.ts index cd2d0a5f2618e0..40b5ee400b0edc 100644 --- a/packages/kbn-es-types/index.ts +++ b/packages/kbn-es-types/index.ts @@ -19,4 +19,7 @@ export type { ESFilter, MaybeReadonlyArray, ClusterDetails, + ESQLColumn, + ESQLRow, + ESQLSearchReponse, } from './src'; diff --git a/packages/kbn-es-types/src/index.ts b/packages/kbn-es-types/src/index.ts index f22e43fc7e7051..2acc88f9068a7a 100644 --- a/packages/kbn-es-types/src/index.ts +++ b/packages/kbn-es-types/src/index.ts @@ -12,6 +12,9 @@ import { AggregateOfMap as AggregationResultOfMap, SearchHit, ClusterDetails, + ESQLColumn, + ESQLRow, + ESQLSearchReponse, } from './search'; export type ESFilter = estypes.QueryDslQueryContainer; @@ -41,4 +44,7 @@ export type { AggregationResultOfMap, SearchHit, ClusterDetails, + ESQLColumn, + ESQLRow, + ESQLSearchReponse, }; diff --git a/packages/kbn-es-types/src/search.ts b/packages/kbn-es-types/src/search.ts index 502a7464e53517..71466c322be423 100644 --- a/packages/kbn-es-types/src/search.ts +++ b/packages/kbn-es-types/src/search.ts @@ -653,3 +653,15 @@ export interface ClusterDetails { _shards?: estypes.ShardStatistics; failures?: estypes.ShardFailure[]; } + +export interface ESQLColumn { + name: string; + type: string; +} + +export type ESQLRow = unknown[]; + +export interface ESQLSearchReponse { + columns: ESQLColumn[]; + values: ESQLRow[]; +} diff --git a/packages/kbn-text-based-editor/src/editor_footer.tsx b/packages/kbn-text-based-editor/src/editor_footer.tsx index b9b183424c77a0..351b7bbe251c19 100644 --- a/packages/kbn-text-based-editor/src/editor_footer.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer.tsx @@ -200,6 +200,7 @@ interface EditorFooterProps { disableSubmitAction?: boolean; editorIsInline?: boolean; isSpaceReduced?: boolean; + isLoading?: boolean; } export const EditorFooter = memo(function EditorFooter({ @@ -214,6 +215,7 @@ export const EditorFooter = memo(function EditorFooter({ disableSubmitAction, editorIsInline, isSpaceReduced, + isLoading, }: EditorFooterProps) { const { euiTheme } = useEuiTheme(); const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false); @@ -331,6 +333,7 @@ export const EditorFooter = memo(function EditorFooter({ size="s" fill onClick={runQuery} + isLoading={isLoading} isDisabled={Boolean(disableSubmitAction)} data-test-subj="TextBasedLangEditor-run-query-button" minWidth={isSpaceReduced ? false : undefined} 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 04e79334cf219f..24966c78960bbc 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 @@ -86,6 +86,8 @@ export interface TextBasedLanguagesEditorProps { errors?: Error[]; /** Warning string as it comes from ES */ warning?: string; + /** Disables the editor and displays loading icon in run button */ + isLoading?: boolean; /** Disables the editor */ isDisabled?: boolean; /** Indicator if the editor is on dark mode */ @@ -149,6 +151,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ detectTimestamp = false, errors: serverErrors, warning: serverWarning, + isLoading, isDisabled, isDarkMode, hideMinimizeButton, @@ -540,7 +543,9 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ }, overviewRulerBorder: false, readOnly: - isDisabled || Boolean(!isCompactFocused && codeOneLiner && codeOneLiner.includes('...')), + isLoading || + isDisabled || + Boolean(!isCompactFocused && codeOneLiner && codeOneLiner.includes('...')), }; if (isCompactFocused) { @@ -836,6 +841,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ disableSubmitAction={disableSubmitAction} hideRunQueryText={hideRunQueryText} isSpaceReduced={isSpaceReduced} + isLoading={isLoading} /> )} @@ -925,6 +931,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ editorIsInline={editorIsInline} disableSubmitAction={disableSubmitAction} isSpaceReduced={isSpaceReduced} + isLoading={isLoading} {...editorMessages} /> )} diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts index e1eb3bb7be4524..f8b0bb04b3096d 100644 --- a/src/plugins/data/common/search/expressions/esql.ts +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -20,6 +20,7 @@ import { zipObject } from 'lodash'; import { Observable, defer, throwError } from 'rxjs'; import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { buildEsQuery } from '@kbn/es-query'; +import type { ESQLSearchReponse } from '@kbn/es-types'; import { getEsQueryConfig } from '../../es_query'; import { getTime } from '../../query'; import { ESQL_SEARCH_STRATEGY, IKibanaSearchRequest, ISearchGeneric, KibanaContext } from '..'; @@ -90,14 +91,6 @@ interface ESQLSearchParams { locale?: string; } -interface ESQLSearchReponse { - columns?: Array<{ - name: string; - type: string; - }>; - values: unknown[][]; -} - export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { const essql: EsqlExpressionFunctionDefinition = { name: 'esql', diff --git a/src/plugins/data/server/search/strategies/es_search/elasticsearch.ts b/src/plugins/data/server/search/strategies/es_search/elasticsearch.ts index 7973f74ee17def..3d3187b20e042b 100644 --- a/src/plugins/data/server/search/strategies/es_search/elasticsearch.ts +++ b/src/plugins/data/server/search/strategies/es_search/elasticsearch.ts @@ -15,9 +15,3 @@ export type IndexAsString = { } & Map; export type Omit = Pick>; - -export interface BoolQuery { - must_not: Array>; - should: Array>; - filter: Array>; -} diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 77cc9b6aa5b54e..12e74fed6a8ec1 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -50,7 +50,8 @@ "@kbn/search-errors", "@kbn/search-response-warnings", "@kbn/shared-ux-link-redirect-app", - "@kbn/bfetch-error" + "@kbn/bfetch-error", + "@kbn/es-types" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 456748a3752f13..143a17ca4511f4 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -69,6 +69,7 @@ export enum SOURCE_TYPES { ES_SEARCH = 'ES_SEARCH', ES_PEW_PEW = 'ES_PEW_PEW', ES_ML_ANOMALIES = 'ML_ANOMALIES', + ESQL = 'ESQL', EMS_XYZ = 'EMS_XYZ', // identifies a custom TMS source. EMS-prefix in the name is a little unfortunate :( WMS = 'WMS', KIBANA_TILEMAP = 'KIBANA_TILEMAP', @@ -327,6 +328,7 @@ export enum WIZARD_ID { POINT_2_POINT = 'point2Point', ES_DOCUMENT = 'esDocument', ES_TOP_HITS = 'esTopHits', + ESQL = 'ESQL', KIBANA_BASEMAP = 'kibanaBasemap', MVT_VECTOR = 'mvtVector', WMS_LAYER = 'wmsLayer', diff --git a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts index 30f02a7a9c4c79..aaa3965307a25c 100644 --- a/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/source_descriptor_types.ts @@ -9,6 +9,7 @@ import { FeatureCollection } from 'geojson'; import type { Query } from '@kbn/es-query'; +import type { ESQLColumn } from '@kbn/es-types'; import { SortDirection } from '@kbn/data-plugin/common/search'; import { AGG_TYPE, @@ -37,6 +38,20 @@ export type EMSFileSourceDescriptor = AbstractSourceDescriptor & { tooltipProperties: string[]; }; +export type ESQLSourceDescriptor = AbstractSourceDescriptor & { + // id: UUID + id: string; + esql: string; + columns: ESQLColumn[]; + /* + * Date field used to narrow ES|QL requests by global time range + */ + dateField?: string; + narrowByGlobalSearch: boolean; + narrowByMapBounds: boolean; + applyForceRefresh: boolean; +}; + export type AbstractESSourceDescriptor = AbstractSourceDescriptor & { // id: UUID id: string; diff --git a/x-pack/plugins/maps/common/telemetry/layer_stats_collector.ts b/x-pack/plugins/maps/common/telemetry/layer_stats_collector.ts index d782e0bd813d07..b942ae3d6a6cfa 100644 --- a/x-pack/plugins/maps/common/telemetry/layer_stats_collector.ts +++ b/x-pack/plugins/maps/common/telemetry/layer_stats_collector.ts @@ -214,6 +214,10 @@ function getLayerKey(layerDescriptor: LayerDescriptor): LAYER_KEYS | null { return LAYER_KEYS.ES_ML_ANOMALIES; } + if (layerDescriptor.sourceDescriptor.type === SOURCE_TYPES.ESQL) { + return LAYER_KEYS.ESQL; + } + if (layerDescriptor.sourceDescriptor.type === SOURCE_TYPES.ES_SEARCH) { const sourceDescriptor = layerDescriptor.sourceDescriptor as ESSearchSourceDescriptor; diff --git a/x-pack/plugins/maps/common/telemetry/types.ts b/x-pack/plugins/maps/common/telemetry/types.ts index 97fedb4d81d508..aac8265311764a 100644 --- a/x-pack/plugins/maps/common/telemetry/types.ts +++ b/x-pack/plugins/maps/common/telemetry/types.ts @@ -27,6 +27,7 @@ export enum LAYER_KEYS { ES_AGG_HEXAGONS = 'es_agg_hexagons', ES_AGG_HEATMAP = 'es_agg_heatmap', ES_ML_ANOMALIES = 'es_ml_anomalies', + ESQL = 'esql', EMS_REGION = 'ems_region', EMS_BASEMAP = 'ems_basemap', KBN_TMS_RASTER = 'kbn_tms_raster', diff --git a/x-pack/plugins/maps/kibana.jsonc b/x-pack/plugins/maps/kibana.jsonc index 3fb66c4d931515..b6bf08329fb44e 100644 --- a/x-pack/plugins/maps/kibana.jsonc +++ b/x-pack/plugins/maps/kibana.jsonc @@ -49,7 +49,8 @@ "kibanaUtils", "usageCollection", "unifiedSearch", - "fieldFormats" + "fieldFormats", + "textBasedLanguages" ], "extraPublicDirs": [ "common" diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index e24da30482d662..7b7785f2033c60 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -74,7 +74,7 @@ import { IVectorStyle } from '../classes/styles/vector/vector_style'; import { notifyLicensedFeatureUsage } from '../licensed_features'; import { IESAggField } from '../classes/fields/agg'; import { IField } from '../classes/fields/field'; -import type { IESSource } from '../classes/sources/es_source'; +import type { IVectorSource } from '../classes/sources/vector_source'; import { getDrawMode, getOpenTOCDetails } from '../selectors/ui_selectors'; import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group'; import { isSpatialJoin } from '../classes/joins/is_spatial_join'; @@ -849,7 +849,7 @@ export function setTileState( } function clearInspectorAdapters(layer: ILayer, adapters: Adapters) { - if (isLayerGroup(layer) || !layer.getSource().isESSource()) { + if (isLayerGroup(layer)) { return; } @@ -857,10 +857,15 @@ function clearInspectorAdapters(layer: ILayer, adapters: Adapters) { adapters.vectorTiles.removeLayer(layer.getId()); } + const source = layer.getSource(); + if ('getInspectorRequestIds' in source) { + (source as IVectorSource).getInspectorRequestIds().forEach((id) => { + adapters.requests!.resetRequest(id); + }); + } + if (adapters.requests && 'getValidJoins' in layer) { - const vectorLayer = layer as IVectorLayer; - adapters.requests!.resetRequest((layer.getSource() as IESSource).getId()); - vectorLayer.getValidJoins().forEach((join) => { + (layer as IVectorLayer).getValidJoins().forEach((join) => { adapters.requests!.resetRequest(join.getRightJoinSource().getId()); }); } diff --git a/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts b/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts index adb53e76c060af..2c3110b8c9cf2f 100644 --- a/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts +++ b/x-pack/plugins/maps/public/classes/layers/build_vector_request_meta.ts @@ -26,7 +26,7 @@ export function buildVectorRequestMeta( applyGlobalQuery: source.getApplyGlobalQuery(), applyGlobalTime: source.getApplyGlobalTime(), sourceMeta: source.getSyncMeta(dataFilters), - applyForceRefresh: source.isESSource() ? source.getApplyForceRefresh() : false, + applyForceRefresh: source.getApplyForceRefresh(), isForceRefresh, isFeatureEditorOpenForLayer, }; diff --git a/x-pack/plugins/maps/public/classes/layers/raster_tile_layer/raster_tile_layer.ts b/x-pack/plugins/maps/public/classes/layers/raster_tile_layer/raster_tile_layer.ts index 9decff440ee499..6712bfeef15765 100644 --- a/x-pack/plugins/maps/public/classes/layers/raster_tile_layer/raster_tile_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/raster_tile_layer/raster_tile_layer.ts @@ -82,7 +82,7 @@ export class RasterTileLayer extends AbstractLayer { ...dataFilters, applyGlobalQuery: source.getApplyGlobalQuery(), applyGlobalTime: source.getApplyGlobalTime(), - applyForceRefresh: source.isESSource() ? source.getApplyForceRefresh() : false, + applyForceRefresh: source.getApplyForceRefresh(), sourceQuery: this.getQuery() || undefined, isForceRefresh, }; diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/layer_wizard_registry.ts b/x-pack/plugins/maps/public/classes/layers/wizards/layer_wizard_registry.ts index a5284fe0a5cbf2..d94b3e4ae6db4c 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/layer_wizard_registry.ts +++ b/x-pack/plugins/maps/public/classes/layers/wizards/layer_wizard_registry.ts @@ -48,6 +48,7 @@ export type LayerWizard = { export type RenderWizardArguments = { previewLayers: (layerDescriptors: LayerDescriptor[]) => void; mapColors: string[]; + mostCommonDataViewId?: string; // multi-step arguments for wizards that supply 'prerequisiteSteps' currentStepId: string | null; isOnFinalStep: boolean; diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts index 7a747ea2533cfe..920cc589c847b2 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/wizards/load_layer_wizards.ts @@ -18,6 +18,7 @@ import { } from '../../sources/es_geo_grid_source'; import { geoLineLayerWizardConfig } from '../../sources/es_geo_line_source'; import { point2PointLayerWizardConfig } from '../../sources/es_pew_pew_source/point_2_point_layer_wizard'; +import { esqlLayerWizardConfig } from '../../sources/esql_source'; import { emsBoundariesLayerWizardConfig } from '../../sources/ems_file_source'; import { emsBaseMapLayerWizardConfig } from '../../sources/ems_tms_source'; import { kibanaBasemapLayerWizardConfig } from '../../sources/kibana_tilemap_source/kibana_base_map_layer_wizard'; @@ -41,10 +42,10 @@ export function registerLayerWizards() { registerLayerWizardInternal(layerGroupWizardConfig); registerLayerWizardInternal(esDocumentsLayerWizardConfig); - registerLayerWizardInternal(choroplethLayerWizardConfig); + registerLayerWizardInternal(esqlLayerWizardConfig); + registerLayerWizardInternal(choroplethLayerWizardConfig); registerLayerWizardInternal(spatialJoinWizardConfig); - registerLayerWizardInternal(point2PointLayerWizardConfig); registerLayerWizardInternal(clustersLayerWizardConfig); registerLayerWizardInternal(heatmapLayerWizardConfig); @@ -52,15 +53,16 @@ export function registerLayerWizards() { registerLayerWizardInternal(esTopHitsLayerWizardConfig); registerLayerWizardInternal(geoLineLayerWizardConfig); + registerLayerWizardInternal(point2PointLayerWizardConfig); + registerLayerWizardInternal(newVectorLayerWizardConfig); + registerLayerWizardInternal(emsBoundariesLayerWizardConfig); registerLayerWizardInternal(emsBaseMapLayerWizardConfig); - registerLayerWizardInternal(newVectorLayerWizardConfig); - registerLayerWizardInternal(kibanaBasemapLayerWizardConfig); - registerLayerWizardInternal(tmsLayerWizardConfig); registerLayerWizardInternal(wmsLayerWizardConfig); + registerLayerWizardInternal(kibanaBasemapLayerWizardConfig); registerLayerWizardInternal(mvtVectorSourceWizardConfig); registerLayerWizardInternal(ObservabilityLayerWizardConfig); registerLayerWizardInternal(SecurityLayerWizardConfig); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index 82bb8fec43234e..cfa89390f15695 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -230,6 +230,18 @@ export class ESGeoLineSource extends AbstractESAggSource { ); } + getInspectorRequestIds(): string[] { + return [this._getTracksRequestId(), this._getEntitiesRequestId()]; + } + + private _getTracksRequestId() { + return `${this.getId()}_tracks`; + } + + private _getEntitiesRequestId() { + return `${this.getId()}_entities`; + } + async _getGeoLineByTimeseries( layerName: string, requestMeta: VectorSourceRequestMeta, @@ -264,7 +276,7 @@ export class ESGeoLineSource extends AbstractESAggSource { const warnings: SearchResponseWarning[] = []; const resp = await this._runEsQuery({ - requestId: `${this.getId()}_tracks`, + requestId: this._getTracksRequestId(), requestName: getLayerFeaturesRequestName(layerName), searchSource, registerCancelCallback, @@ -356,7 +368,7 @@ export class ESGeoLineSource extends AbstractESAggSource { } const entityResp = await this._runEsQuery({ - requestId: `${this.getId()}_entities`, + requestId: this._getEntitiesRequestId(), requestName: i18n.translate('xpack.maps.source.esGeoLine.entityRequestName', { defaultMessage: `load track entities ({layerName})`, values: { @@ -431,7 +443,7 @@ export class ESGeoLineSource extends AbstractESAggSource { }, }); const tracksResp = await this._runEsQuery({ - requestId: `${this.getId()}_tracks`, + requestId: this._getTracksRequestId(), requestName: getLayerFeaturesRequestName(layerName), searchSource: tracksSearchSource, registerCancelCallback, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 5d0f6aa59c55d2..161acd3e5db737 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -568,6 +568,10 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return true; } + getInspectorRequestIds(): string[] { + return [this.getId(), this._getFeaturesCountRequestId()]; + } + async getGeoJsonWithMeta( layerName: string, requestMeta: VectorSourceRequestMeta, @@ -992,6 +996,10 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return !isWithin; } + private _getFeaturesCountRequestId() { + return this.getId() + 'features_count'; + } + async canLoadAllDocuments( layerName: string, requestMeta: VectorSourceRequestMeta, @@ -1003,7 +1011,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource const searchSource = await this.makeSearchSource(requestMeta, 0); searchSource.setField('trackTotalHits', maxResultWindow + 1); const resp = await this._runEsQuery({ - requestId: this.getId() + 'features_count', + requestId: this._getFeaturesCountRequestId(), requestName: i18n.translate('xpack.maps.vectorSource.featuresCountRequestName', { defaultMessage: 'load features count ({layerName})', values: { layerName }, diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 2b5ec413ba6ec9..aa41f33efa00b6 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -120,6 +120,10 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource return this._descriptor.id; } + getInspectorRequestIds(): string[] { + return [this.getId()]; + } + getApplyGlobalQuery(): boolean { return this._descriptor.applyGlobalQuery; } diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.test.ts b/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.test.ts new file mode 100644 index 00000000000000..fbbbc697376f8c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { convertToGeoJson } from './convert_to_geojson'; + +describe('convertToGeoJson', () => { + test('should convert ES|QL response to feature collection', () => { + const resp = { + columns: [ + { name: 'location', type: 'geo_point' }, + { name: 'bytes', type: 'long' }, + ], + values: [ + ['POINT (-87.66208335757256 32.68147221766412)', 6901], + ['POINT (-76.41376560553908 39.32566332165152)', 484], + ], + }; + const featureCollection = convertToGeoJson(resp); + expect(featureCollection).toEqual({ + type: 'FeatureCollection', + features: [ + { + geometry: { + coordinates: [-87.66208335757256, 32.68147221766412], + type: 'Point', + }, + properties: { + bytes: 6901, + }, + type: 'Feature', + }, + { + geometry: { + coordinates: [-76.41376560553908, 39.32566332165152], + type: 'Point', + }, + properties: { + bytes: 484, + }, + type: 'Feature', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts b/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts new file mode 100644 index 00000000000000..3940cd9102c540 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// @ts-ignore +import { parse } from 'wellknown'; +import { Feature, FeatureCollection, GeoJsonProperties } from 'geojson'; +import type { ESQLSearchReponse } from '@kbn/es-types'; +import { getGeometryColumnIndex } from './esql_utils'; + +export function convertToGeoJson(resp: ESQLSearchReponse): FeatureCollection { + const geometryIndex = getGeometryColumnIndex(resp.columns); + const features: Feature[] = []; + for (let i = 0; i < resp.values.length; i++) { + const hit = resp.values[i]; + const wkt = hit[geometryIndex]; + if (!wkt) { + continue; + } + try { + const geometry = parse(wkt); + const properties: GeoJsonProperties = {}; + for (let j = 0; j < hit.length; j++) { + // do not store geometry in properties + if (j === geometryIndex) { + continue; + } + properties[resp.columns[j].name] = hit[j] as unknown; + } + features.push({ + type: 'Feature', + geometry, + properties, + }); + } catch (parseError) { + // TODO surface parse error in some kind of warning + } + } + + return { + type: 'FeatureCollection', + features, + }; +} diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/create_source_editor.tsx new file mode 100644 index 00000000000000..20670e0121c72c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/create_source_editor.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import { EuiSkeletonText } from '@elastic/eui'; +import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; +import type { ESQLSourceDescriptor } from '../../../../common/descriptor_types'; +import { getIndexPatternService } from '../../../kibana_services'; +import { ESQLEditor } from './esql_editor'; +import { ESQL_GEO_POINT_TYPE } from './esql_utils'; + +interface Props { + mostCommonDataViewId?: string; + onSourceConfigChange: (sourceConfig: Partial | null) => void; +} + +export function CreateSourceEditor(props: Props) { + const [isInitialized, setIsInitialized] = useState(false); + const [esql, setEsql] = useState(''); + const [dateField, setDateField] = useState(); + + useEffect(() => { + let ignore = false; + + function getDataView() { + return props.mostCommonDataViewId + ? getIndexPatternService().get(props.mostCommonDataViewId) + : getIndexPatternService().getDefaultDataView(); + } + + getDataView() + .then((dataView) => { + if (ignore) { + return; + } + + if (dataView) { + let geoField: string | undefined; + const initialDateFields: string[] = []; + for (let i = 0; i < dataView.fields.length; i++) { + const field = dataView.fields[i]; + if (!geoField && ES_GEO_FIELD_TYPE.GEO_POINT === field.type) { + geoField = field.name; + } else if ('date' === field.type) { + initialDateFields.push(field.name); + } + } + + if (geoField) { + let initialDateField: string | undefined; + if (dataView.timeFieldName) { + initialDateField = dataView.timeFieldName; + } else if (initialDateFields.length) { + initialDateField = initialDateFields[0]; + } + const initialEsql = `from ${dataView.getIndexPattern()} | keep ${geoField} | limit 10000`; + setDateField(initialDateField); + setEsql(initialEsql); + props.onSourceConfigChange({ + columns: [ + { + name: geoField, + type: ESQL_GEO_POINT_TYPE, + }, + ], + dateField: initialDateField, + esql: initialEsql, + }); + } + } + setIsInitialized(true); + }) + .catch((err) => { + if (ignore) { + return; + } + setIsInitialized(true); + }); + + return () => { + ignore = true; + }; + // only run on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + { + let nextDateField = dateField; + if (!dateField || !change.dateFields.includes(dateField)) { + nextDateField = change.dateFields.length ? change.dateFields[0] : undefined; + } + setDateField(nextDateField); + setEsql(change.esql); + const sourceConfig = + change.esql && change.esql.length + ? { + columns: change.columns, + dateField: nextDateField, + esql: change.esql, + } + : null; + props.onSourceConfigChange(sourceConfig); + }} + /> + + ); +} diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx new file mode 100644 index 00000000000000..fbc002e3c2d4c8 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; +import useMountedState from 'react-use/lib/useMountedState'; +import type { AggregateQuery } from '@kbn/es-query'; +import type { ESQLColumn } from '@kbn/es-types'; +import { TextBasedLangEditor } from '@kbn/text-based-languages/public'; +import { getESQLMeta, verifyGeometryColumn } from './esql_utils'; + +interface Props { + esql: string; + onESQLChange: ({ + columns, + dateFields, + esql, + }: { + columns: ESQLColumn[]; + dateFields: string[]; + esql: string; + }) => void; +} + +export function ESQLEditor(props: Props) { + const isMounted = useMountedState(); + + const [error, setError] = useState(); + const [warning, setWarning] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [localQuery, setLocalQuery] = useState({ esql: props.esql }); + + return ( + <> + { + if (!query) { + return; + } + + if (warning) { + setWarning(undefined); + } + if (error) { + setError(undefined); + } + setIsLoading(true); + + try { + const esql = (query as { esql: string }).esql; + const esqlMeta = await getESQLMeta(esql); + if (!isMounted()) { + return; + } + verifyGeometryColumn(esqlMeta.columns); + if (esqlMeta.columns.length >= 6) { + setWarning( + i18n.translate('xpack.maps.esqlSource.tooManyColumnsWarning', { + defaultMessage: `ES|QL statement returns {count} columns. For faster maps, use 'DROP' or 'KEEP' to narrow columns.`, + values: { + count: esqlMeta.columns.length, + }, + }) + ); + } + props.onESQLChange({ + columns: esqlMeta.columns, + dateFields: esqlMeta.dateFields, + esql, + }); + } catch (err) { + if (!isMounted()) { + return; + } + setError(err); + props.onESQLChange({ + columns: [], + dateFields: [], + esql: '', + }); + } + + setIsLoading(false); + }} + errors={error ? [error] : undefined} + warning={warning} + expandCodeEditor={(status: boolean) => { + // never called because hideMinimizeButton hides UI + }} + isCodeEditorExpanded + hideMinimizeButton + editorIsInline + hideRunQueryText + isLoading={isLoading} + disableSubmitAction={isEqual(localQuery, props.esql)} + /> + + ); +} diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_layer_wizard.tsx new file mode 100644 index 00000000000000..c01ca307fbaf47 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_layer_wizard.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { CreateSourceEditor } from './create_source_editor'; +import { LayerWizard, RenderWizardArguments } from '../../layers'; +import { sourceTitle, ESQLSource } from './esql_source'; +import { LAYER_WIZARD_CATEGORY, WIZARD_ID } from '../../../../common/constants'; +import type { ESQLSourceDescriptor } from '../../../../common/descriptor_types'; +import { GeoJsonVectorLayer } from '../../layers/vector_layer'; +import { DocumentsLayerIcon } from '../../layers/wizards/icons/documents_layer_icon'; + +export const esqlLayerWizardConfig: LayerWizard = { + id: WIZARD_ID.ESQL, + order: 10, + categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], + description: i18n.translate('xpack.maps.source.esqlDescription', { + defaultMessage: 'Create a map layer using the Elasticsearch Query Language', + }), + icon: DocumentsLayerIcon, + isBeta: true, + renderWizard: ({ previewLayers, mapColors, mostCommonDataViewId }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: Partial | null) => { + if (!sourceConfig) { + previewLayers([]); + return; + } + + const sourceDescriptor = ESQLSource.createDescriptor(sourceConfig); + const layerDescriptor = GeoJsonVectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + previewLayers([layerDescriptor]); + }; + + return ( + + ); + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx new file mode 100644 index 00000000000000..b92ccd1fb82f95 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx @@ -0,0 +1,292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { lastValueFrom } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { v4 as uuidv4 } from 'uuid'; +import { Adapters } from '@kbn/inspector-plugin/common/adapters'; +import { buildEsQuery, getIndexPatternFromESQLQuery, getLimitFromESQLQuery } from '@kbn/es-query'; +import type { BoolQuery, Filter, Query } from '@kbn/es-query'; +import type { ESQLSearchReponse } from '@kbn/es-types'; +import { getEsQueryConfig } from '@kbn/data-service/src/es_query'; +import { getTime } from '@kbn/data-plugin/public'; +import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; +import type { + ESQLSourceDescriptor, + VectorSourceRequestMeta, +} from '../../../../common/descriptor_types'; +import { createExtentFilter } from '../../../../common/elasticsearch_util'; +import { DataRequest } from '../../util/data_request'; +import { isValidStringConfig } from '../../util/valid_string_config'; +import type { SourceEditorArgs } from '../source'; +import { AbstractVectorSource, getLayerFeaturesRequestName } from '../vector_source'; +import type { IVectorSource, GeoJsonWithMeta, SourceStatus } from '../vector_source'; +import type { IField } from '../../fields/field'; +import { InlineField } from '../../fields/inline_field'; +import { getData, getUiSettings } from '../../../kibana_services'; +import { convertToGeoJson } from './convert_to_geojson'; +import { getFieldType, getGeometryColumnIndex } from './esql_utils'; +import { UpdateSourceEditor } from './update_source_editor'; + +type ESQLSourceSyncMeta = Pick< + ESQLSourceDescriptor, + 'columns' | 'dateField' | 'esql' | 'narrowByMapBounds' +>; + +export const sourceTitle = i18n.translate('xpack.maps.source.esqlSearchTitle', { + defaultMessage: 'ES|QL', +}); + +export class ESQLSource extends AbstractVectorSource implements IVectorSource { + readonly _descriptor: ESQLSourceDescriptor; + + static createDescriptor(descriptor: Partial): ESQLSourceDescriptor { + if (!isValidStringConfig(descriptor.esql)) { + throw new Error('Cannot create ESQLSourceDescriptor when esql is not provided'); + } + return { + ...descriptor, + id: isValidStringConfig(descriptor.id) ? descriptor.id! : uuidv4(), + type: SOURCE_TYPES.ESQL, + esql: descriptor.esql!, + columns: descriptor.columns ? descriptor.columns : [], + narrowByGlobalSearch: + typeof descriptor.narrowByGlobalSearch !== 'undefined' + ? descriptor.narrowByGlobalSearch + : true, + narrowByMapBounds: + typeof descriptor.narrowByMapBounds !== 'undefined' ? descriptor.narrowByMapBounds : true, + applyForceRefresh: + typeof descriptor.applyForceRefresh !== 'undefined' ? descriptor.applyForceRefresh : true, + }; + } + + constructor(descriptor: ESQLSourceDescriptor) { + super(ESQLSource.createDescriptor(descriptor)); + this._descriptor = descriptor; + } + + private _getRequestId(): string { + return this._descriptor.id; + } + + async getDisplayName() { + const pattern: string = getIndexPatternFromESQLQuery(this._descriptor.esql); + return pattern ? pattern : 'ES|QL'; + } + + async supportsFitToBounds(): Promise { + return false; + } + + getInspectorRequestIds() { + return [this._getRequestId()]; + } + + isQueryAware() { + return true; + } + + getApplyGlobalQuery() { + return this._descriptor.narrowByGlobalSearch; + } + + async isTimeAware() { + return !!this._descriptor.dateField; + } + + getApplyGlobalTime() { + return !!this._descriptor.dateField; + } + + getApplyForceRefresh() { + return this._descriptor.applyForceRefresh; + } + + isFilterByMapBounds() { + return this._descriptor.narrowByMapBounds; + } + + async getSupportedShapeTypes() { + return [VECTOR_SHAPE_TYPE.POINT]; + } + + supportsJoins() { + return false; // Joins will be part of ESQL statement and not client side join + } + + async getGeoJsonWithMeta( + layerName: string, + requestMeta: VectorSourceRequestMeta, + registerCancelCallback: (callback: () => void) => void, + isRequestStillActive: () => boolean, + inspectorAdapters: Adapters + ): Promise { + const limit = getLimitFromESQLQuery(this._descriptor.esql); + const params: { query: string; filter?: { bool: BoolQuery } } = { + query: this._descriptor.esql, + }; + + const query: Query[] = []; + const filters: Filter[] = []; + if (this._descriptor.narrowByGlobalSearch) { + if (requestMeta.query) { + query.push(requestMeta.query); + } + if (requestMeta.embeddableSearchContext?.query) { + query.push(requestMeta.embeddableSearchContext.query); + } + filters.push(...requestMeta.filters); + if (requestMeta.embeddableSearchContext) { + filters.push(...requestMeta.embeddableSearchContext.filters); + } + } + + if (this._descriptor.narrowByMapBounds && requestMeta.buffer) { + const geoField = + this._descriptor.columns[getGeometryColumnIndex(this._descriptor.columns)]?.name; + if (geoField) { + const extentFilter = createExtentFilter(requestMeta.buffer, [geoField]); + filters.push(extentFilter); + } + } + + if (requestMeta.applyGlobalTime) { + const timeRange = requestMeta.timeslice + ? { + from: new Date(requestMeta.timeslice.from).toISOString(), + to: new Date(requestMeta.timeslice.to).toISOString(), + mode: 'absolute' as 'absolute', + } + : requestMeta.timeFilters; + const timeFilter = getTime(undefined, timeRange, { + fieldName: this._descriptor.dateField, + }); + if (timeFilter) { + filters.push(timeFilter); + } + } + + params.filter = buildEsQuery(undefined, query, filters, getEsQueryConfig(getUiSettings())); + + const requestResponder = inspectorAdapters.requests!.start( + getLayerFeaturesRequestName(layerName), + { + id: this._getRequestId(), + } + ); + requestResponder.json(params); + + const { rawResponse, requestParams } = await lastValueFrom( + getData() + .search.search( + { params }, + { + strategy: 'esql', + } + ) + .pipe( + tap({ + error(error) { + requestResponder.error({ + json: 'attributes' in error ? error.attributes : { message: error.message }, + }); + }, + }) + ) + ); + + requestResponder.ok({ json: rawResponse, requestParams }); + + const esqlSearchResponse = rawResponse as unknown as ESQLSearchReponse; + const resultsCount = esqlSearchResponse.values.length; + return { + data: convertToGeoJson(esqlSearchResponse), + meta: { + resultsCount, + areResultsTrimmed: resultsCount >= limit, + }, + }; + } + + getSourceStatus(sourceDataRequest?: DataRequest): SourceStatus { + const meta = sourceDataRequest ? sourceDataRequest.getMeta() : null; + if (!meta) { + // no tooltip content needed when there is no feature collection or meta + return { + tooltipContent: null, + areResultsTrimmed: false, + }; + } + + if (meta.areResultsTrimmed) { + return { + tooltipContent: i18n.translate('xpack.maps.esqlSearch.resultsTrimmedMsg', { + defaultMessage: `Results limited to first {count} rows.`, + values: { count: meta.resultsCount?.toLocaleString() }, + }), + areResultsTrimmed: true, + }; + } + + return { + tooltipContent: i18n.translate('xpack.maps.esqlSearch.rowCountMsg', { + defaultMessage: `Found {count} rows.`, + values: { count: meta.resultsCount?.toLocaleString() }, + }), + areResultsTrimmed: false, + }; + } + + getFieldByName(fieldName: string): IField | null { + const column = this._descriptor.columns.find(({ name }) => { + return name === fieldName; + }); + const fieldType = column ? getFieldType(column) : undefined; + return column && fieldType + ? new InlineField({ + fieldName: column.name, + source: this, + origin: FIELD_ORIGIN.SOURCE, + dataType: fieldType, + }) + : null; + } + + async getFields() { + const fields: IField[] = []; + this._descriptor.columns.forEach((column) => { + const fieldType = getFieldType(column); + if (fieldType) { + fields.push( + new InlineField({ + fieldName: column.name, + source: this, + origin: FIELD_ORIGIN.SOURCE, + dataType: fieldType, + }) + ); + } + }); + return fields; + } + + renderSourceSettingsEditor({ onChange }: SourceEditorArgs) { + return ; + } + + getSyncMeta(): ESQLSourceSyncMeta { + return { + columns: this._descriptor.columns, + dateField: this._descriptor.dateField, + esql: this._descriptor.esql, + narrowByMapBounds: this._descriptor.narrowByMapBounds, + }; + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts new file mode 100644 index 00000000000000..79cd2aaf70b500 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { lastValueFrom } from 'rxjs'; +import { getIndexPatternFromESQLQuery } from '@kbn/es-query'; +import type { ESQLColumn } from '@kbn/es-types'; +import { getData, getIndexPatternService } from '../../../kibana_services'; + +export const ESQL_GEO_POINT_TYPE = 'geo_point'; + +const NO_GEOMETRY_COLUMN_ERROR_MSG = i18n.translate( + 'xpack.maps.source.esql.noGeometryColumnErrorMsg', + { + defaultMessage: 'Elasticsearch ES|QL query does not have a geometry column.', + } +); + +function isGeometryColumn(column: ESQLColumn) { + return column.type === ESQL_GEO_POINT_TYPE; +} + +export function verifyGeometryColumn(columns: ESQLColumn[]) { + const geometryColumns = columns.filter(isGeometryColumn); + if (geometryColumns.length === 0) { + throw new Error(NO_GEOMETRY_COLUMN_ERROR_MSG); + } + + if (geometryColumns.length > 1) { + throw new Error( + i18n.translate('xpack.maps.source.esql.multipleGeometryColumnErrorMsg', { + defaultMessage: `Elasticsearch ES|QL query has {count} geometry columns when only 1 is allowed. Use 'DROP' or 'KEEP' to narrow columns.`, + values: { + count: geometryColumns.length, + }, + }) + ); + } +} + +export function getGeometryColumnIndex(columns: ESQLColumn[]) { + const index = columns.findIndex(isGeometryColumn); + if (index === -1) { + throw new Error(NO_GEOMETRY_COLUMN_ERROR_MSG); + } + return index; +} + +export async function getESQLMeta(esql: string) { + return { + columns: await getColumns(esql), + dateFields: await getDateFields(esql), + }; +} + +/* + * Map column.type to field type + * Supported column types https://www.elastic.co/guide/en/elasticsearch/reference/master/esql-limitations.html#_supported_types + */ +export function getFieldType(column: ESQLColumn) { + switch (column.type) { + case 'boolean': + case 'date': + case 'ip': + case 'keyword': + case 'text': + return 'string'; + case 'double': + case 'int': + case 'long': + case 'unsigned_long': + return 'number'; + default: + return undefined; + } +} + +async function getColumns(esql: string) { + const params = { + query: esql + ' | limit 0', + }; + + try { + const resp = await lastValueFrom( + getData().search.search( + { params }, + { + strategy: 'esql', + } + ) + ); + + return (resp.rawResponse as unknown as { columns: ESQLColumn[] }).columns; + } catch (error) { + throw new Error( + i18n.translate('xpack.maps.source.esql.getColumnsErrorMsg', { + defaultMessage: 'Unable to load columns. {errorMessage}', + values: { errorMessage: error.message }, + }) + ); + } +} + +export async function getDateFields(esql: string) { + const pattern: string = getIndexPatternFromESQLQuery(esql); + try { + // TODO pass field type filter to getFieldsForWildcard when field type filtering is supported + return (await getIndexPatternService().getFieldsForWildcard({ pattern })) + .filter((field) => { + return field.type === 'date'; + }) + .map((field) => { + return field.name; + }); + } catch (error) { + throw new Error( + i18n.translate('xpack.maps.source.esql.getFieldsErrorMsg', { + defaultMessage: `Unable to load date fields from index pattern: {pattern}. {errorMessage}`, + values: { + errorMessage: error.message, + pattern, + }, + }) + ); + } +} diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/index.ts b/x-pack/plugins/maps/public/classes/sources/esql_source/index.ts new file mode 100644 index 00000000000000..08cf25c30f6a6d --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ESQLSource } from './esql_source'; +export { esqlLayerWizardConfig } from './esql_layer_wizard'; diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/update_source_editor.tsx new file mode 100644 index 00000000000000..0c7e41e2f624db --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/update_source_editor.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ChangeEvent, useEffect, useMemo, useState } from 'react'; +import { + EuiFormRow, + EuiPanel, + EuiSelect, + EuiSkeletonText, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { getIndexPatternFromESQLQuery } from '@kbn/es-query'; +import type { ESQLSourceDescriptor } from '../../../../common/descriptor_types'; +import type { OnSourceChangeArgs } from '../source'; +import { ForceRefreshCheckbox } from '../../../components/force_refresh_checkbox'; +import { ESQLEditor } from './esql_editor'; +import { getDateFields } from './esql_utils'; + +interface Props { + onChange(...args: OnSourceChangeArgs[]): void; + sourceDescriptor: ESQLSourceDescriptor; +} + +export function UpdateSourceEditor(props: Props) { + const [dateFields, setDateFields] = useState([]); + const [isInitialized, setIsInitialized] = useState(false); + + useEffect(() => { + let ignore = false; + getDateFields(props.sourceDescriptor.esql) + .then((initialDateFields) => { + if (ignore) { + return; + } + setDateFields(initialDateFields); + setIsInitialized(true); + }) + .catch((err) => { + if (ignore) { + return; + } + setIsInitialized(true); + }); + + return () => { + ignore = true; + }; + // only run on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const dateSelectOptions = useMemo(() => { + return dateFields.map((dateField) => { + return { + value: dateField, + text: dateField, + }; + }); + }, [dateFields]); + + const narrowByTimeInput = ( + { + if (!event.target.checked) { + props.onChange({ propName: 'dateField', value: undefined }); + return; + } + + if (dateFields.length) { + props.onChange({ propName: 'dateField', value: dateFields[0] }); + } + }} + disabled={dateFields.length === 0} + compressed + /> + ); + + return ( + <> + + +
+ {i18n.translate('xpack.maps.esqlSearch.sourceEditorTitle', { + defaultMessage: 'ES|QL', + })} +
+
+ + + + + { + setDateFields(change.dateFields); + const changes: OnSourceChangeArgs[] = [ + { propName: 'columns', value: change.columns }, + { propName: 'esql', value: change.esql }, + ]; + if ( + props.sourceDescriptor.dateField && + !change.dateFields.includes(props.sourceDescriptor.dateField) + ) { + changes.push({ + propName: 'dateField', + value: change.dateFields.length ? change.dateFields[0] : undefined, + }); + } + props.onChange(...changes); + }} + /> + + + + + { + props.onChange({ propName: 'narrowByMapBounds', value: event.target.checked }); + }} + compressed + /> + + + + { + props.onChange({ propName: 'narrowByGlobalSearch', value: event.target.checked }); + }} + compressed + /> + + + + {dateFields.length === 0 ? ( + + {narrowByTimeInput} + + ) : ( + narrowByTimeInput + )} + + + {props.sourceDescriptor.dateField && ( + + ) => { + props.onChange({ propName: 'dateField', value: e.target.value }); + }} + compressed + /> + + )} + + { + props.onChange({ propName: 'applyForceRefresh', value: applyForceRefresh }); + }} + /> + +
+ + + ); +} diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index a4debb51e32813..ee7e46c06ca0b5 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -229,4 +229,8 @@ export class MVTSingleLayerVectorSource extends AbstractSource implements IMvtVe // Its not possible to filter by geometry for vector tile sources since there is no way to get original geometry return []; } + + getInspectorRequestIds(): string[] { + return []; + } } diff --git a/x-pack/plugins/maps/public/classes/sources/setup_sources.ts b/x-pack/plugins/maps/public/classes/sources/setup_sources.ts index 91e2f241ed83b9..3e65232ff9a4f1 100644 --- a/x-pack/plugins/maps/public/classes/sources/setup_sources.ts +++ b/x-pack/plugins/maps/public/classes/sources/setup_sources.ts @@ -13,6 +13,7 @@ import { ESGeoGridSource } from './es_geo_grid_source'; import { ESGeoLineSource } from './es_geo_line_source'; import { ESPewPewSource } from './es_pew_pew_source'; import { ESSearchSource } from './es_search_source'; +import { ESQLSource } from './esql_source'; import { GeoJsonFileSource } from './geojson_file_source'; import { KibanaTilemapSource } from './kibana_tilemap_source'; import { MVTSingleLayerVectorSource } from './mvt_single_layer_vector_source'; @@ -56,6 +57,11 @@ export function setupSources() { type: SOURCE_TYPES.ES_SEARCH, }); + registerSource({ + ConstructorFunction: ESQLSource, + type: SOURCE_TYPES.ESQL, + }); + registerSource({ ConstructorFunction: GeoJsonFileSource, type: SOURCE_TYPES.GEOJSON_FILE, diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 0d760a9ca1d6b6..a2a18b79a09282 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -59,6 +59,9 @@ export interface ISource { isTimeAware(): Promise; getImmutableProperties(dataFilters: DataFilters): Promise; getAttributionProvider(): (() => Promise) | null; + /* + * Returns true when source implements IESSource interface + */ isESSource(): boolean; renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null; supportsFitToBounds(): Promise; diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 5adaf6ec20c426..c5aac6a5a7efc0 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -133,6 +133,11 @@ export interface IVectorSource extends ISource { mbFeature, onClose, }: GetFeatureActionsArgs): TooltipFeatureAction[]; + + /* + * Provide unique ids for managing source requests in Inspector + */ + getInspectorRequestIds(): string[]; } export class AbstractVectorSource extends AbstractSource implements IVectorSource { @@ -178,7 +183,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc isRequestStillActive: () => boolean, inspectorAdapters: Adapters ): Promise { - throw new Error('Should implement VectorSource#getGeoJson'); + throw new Error('Should implement VectorSource#getGeoJsonWithMeta'); } hasTooltipProperties() { @@ -285,4 +290,8 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc ] : []; } + + getInspectorRequestIds(): string[] { + return []; + } } diff --git a/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx b/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx index b705d1a6dce218..0cbd02ec7b0a7e 100644 --- a/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx +++ b/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx @@ -24,12 +24,12 @@ export function ForceRefreshCheckbox({ applyForceRefresh, setApplyForceRefresh } { const renderWizardArgs = { previewLayers: props.previewLayers, mapColors: props.mapColors, + mostCommonDataViewId: props.mostCommonDataViewId, currentStepId: props.currentStepId, isOnFinalStep: props.isOnFinalStep, enableNextBtn: props.enableNextBtn, diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/index.ts b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/index.ts index ed30f290b4b98f..bb80d4c8b44258 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/index.ts +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/index.ts @@ -8,11 +8,12 @@ import { connect } from 'react-redux'; import { FlyoutBody } from './flyout_body'; import { MapStoreState } from '../../../reducers/store'; -import { getMapColors } from '../../../selectors/map_selectors'; +import { getMapColors, getMostCommonDataViewId } from '../../../selectors/map_selectors'; function mapStateToProps(state: MapStoreState) { return { mapColors: getMapColors(state), + mostCommonDataViewId: getMostCommonDataViewId(state), }; } diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx index 62a69931fbacdf..5c95facbde696f 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx @@ -160,7 +160,13 @@ export class LayerWizardSelect extends Component { { + const counts: { [key: string]: number } = {}; + function incrementCount(ids: string[]) { + ids.forEach((id) => { + const count = counts.hasOwnProperty(id) ? counts[id] : 0; + counts[id] = count + 1; + }); + } + + if (waitingForMapReadyLayerList.length) { + waitingForMapReadyLayerList.forEach((layerDescriptor) => { + const layer = createLayerInstance(layerDescriptor, []); // custom icons not needed, layer instance only used to get index pattern ids + incrementCount(layer.getIndexPatternIds()); + }); + } else { + layerList.forEach((layer) => { + incrementCount(layer.getIndexPatternIds()); + }); + } + + let mostCommonId: string | undefined; + let mostCommonCount = 0; + Object.keys(counts).forEach((id) => { + if (counts[id] > mostCommonCount) { + mostCommonId = id; + mostCommonCount = counts[id]; + } + }); + + return mostCommonId; + } +); + export const getGeoFieldNames = createSelector( getLayerList, getWaitingForMapReadyLayerListRaw, diff --git a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts index 35cc272725eabe..f205cf531267de 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/collectors/register.ts @@ -120,6 +120,24 @@ export function registerMapsUsageCollector(usageCollection?: UsageCollectionSetu _meta: { description: 'total number of es machine learning anomaly layers in cluster' }, }, }, + esql: { + min: { + type: 'long', + _meta: { description: 'min number of ES|QL layers per map' }, + }, + max: { + type: 'long', + _meta: { description: 'max number of ES|QL layers per map' }, + }, + avg: { + type: 'float', + _meta: { description: 'avg number of ES|QL layers per map' }, + }, + total: { + type: 'long', + _meta: { description: 'total number of ES|QL layers in cluster' }, + }, + }, es_point_to_point: { min: { type: 'long', diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index d2972dcd3e6f38..eeef6e58815bb7 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -78,6 +78,9 @@ "@kbn/search-response-warnings", "@kbn/calculate-width-from-char-count", "@kbn/content-management-table-list-view-common", + "@kbn/text-based-languages", + "@kbn/es-types", + "@kbn/data-service", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ml/public/maps/anomaly_source.tsx b/x-pack/plugins/ml/public/maps/anomaly_source.tsx index 416a820e845b9e..27d43eeb95771c 100644 --- a/x-pack/plugins/ml/public/maps/anomaly_source.tsx +++ b/x-pack/plugins/ml/public/maps/anomaly_source.tsx @@ -388,4 +388,8 @@ export class AnomalySource implements IVectorSource { async getDefaultFields(): Promise>> { return {}; } + + getInspectorRequestIds() { + return []; + } } diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index a7098dae6a150d..6f7afd7d124650 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -8222,6 +8222,34 @@ } } }, + "esql": { + "properties": { + "min": { + "type": "long", + "_meta": { + "description": "min number of ES|QL layers per map" + } + }, + "max": { + "type": "long", + "_meta": { + "description": "max number of ES|QL layers per map" + } + }, + "avg": { + "type": "float", + "_meta": { + "description": "avg number of ES|QL layers per map" + } + }, + "total": { + "type": "long", + "_meta": { + "description": "total number of ES|QL layers in cluster" + } + } + } + }, "es_point_to_point": { "properties": { "min": { diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index b82e7a53437461..92ae21c7c09c04 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -51,27 +51,28 @@ export default function ({ getService }: FtrProviderContext) { delete mapUsage.timeCaptured; expect(mapUsage).eql({ - mapsTotalCount: 27, + mapsTotalCount: 28, basemaps: {}, - joins: { term: { min: 1, max: 1, total: 4, avg: 0.14814814814814814 } }, + joins: { term: { min: 1, max: 1, total: 4, avg: 0.14285714285714285 } }, layerTypes: { - es_docs: { min: 1, max: 3, total: 20, avg: 0.7407407407407407 }, - es_agg_grids: { min: 1, max: 1, total: 6, avg: 0.2222222222222222 }, - es_point_to_point: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, - es_top_hits: { min: 1, max: 1, total: 2, avg: 0.07407407407407407 }, - es_agg_heatmap: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, - kbn_tms_raster: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, - ems_basemap: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, - ems_region: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, + es_docs: { min: 1, max: 3, total: 20, avg: 0.7142857142857143 }, + es_agg_grids: { min: 1, max: 1, total: 6, avg: 0.21428571428571427 }, + es_point_to_point: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, + es_top_hits: { min: 1, max: 1, total: 2, avg: 0.07142857142857142 }, + es_agg_heatmap: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, + esql: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, + kbn_tms_raster: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, + ems_basemap: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, + ems_region: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, }, resolutions: { - coarse: { min: 1, max: 1, total: 4, avg: 0.14814814814814814 }, - super_fine: { min: 1, max: 1, total: 3, avg: 0.1111111111111111 }, + coarse: { min: 1, max: 1, total: 4, avg: 0.14285714285714285 }, + super_fine: { min: 1, max: 1, total: 3, avg: 0.10714285714285714 }, }, scalingOptions: { - limit: { min: 1, max: 3, total: 15, avg: 0.5555555555555556 }, - clusters: { min: 1, max: 1, total: 1, avg: 0.037037037037037035 }, - mvt: { min: 1, max: 1, total: 4, avg: 0.14814814814814814 }, + limit: { min: 1, max: 3, total: 15, avg: 0.5357142857142857 }, + clusters: { min: 1, max: 1, total: 1, avg: 0.03571428571428571 }, + mvt: { min: 1, max: 1, total: 4, avg: 0.14285714285714285 }, }, attributesPerMap: { customIconsCount: { @@ -80,51 +81,51 @@ export default function ({ getService }: FtrProviderContext) { min: 0, }, dataSourcesCount: { - avg: 1.1851851851851851, + avg: 1.1785714285714286, max: 6, min: 1, }, emsVectorLayersCount: { idThatDoesNotExitForEMSFileSource: { - avg: 0.037037037037037035, + avg: 0.03571428571428571, max: 1, min: 1, }, }, layerTypesCount: { BLENDED_VECTOR: { - avg: 0.037037037037037035, + avg: 0.03571428571428571, max: 1, min: 1, }, EMS_VECTOR_TILE: { - avg: 0.037037037037037035, + avg: 0.03571428571428571, max: 1, min: 1, }, GEOJSON_VECTOR: { - avg: 0.8148148148148148, + avg: 0.8214285714285714, max: 5, min: 1, }, HEATMAP: { - avg: 0.037037037037037035, + avg: 0.03571428571428571, max: 1, min: 1, }, MVT_VECTOR: { - avg: 0.25925925925925924, + avg: 0.25, max: 1, min: 1, }, RASTER_TILE: { - avg: 0.037037037037037035, + avg: 0.03571428571428571, max: 1, min: 1, }, }, layersCount: { - avg: 1.2222222222222223, + avg: 1.2142857142857142, max: 7, min: 1, }, diff --git a/x-pack/test/functional/apps/maps/group1/esql_source.ts b/x-pack/test/functional/apps/maps/group1/esql_source.ts new file mode 100644 index 00000000000000..8bedf59e3f6b44 --- /dev/null +++ b/x-pack/test/functional/apps/maps/group1/esql_source.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['maps']); + const security = getService('security'); + + describe('esql', () => { + before(async () => { + await security.testUser.setRoles(['global_maps_all', 'test_logstash_reader'], { + skipBrowserRefresh: true, + }); + await PageObjects.maps.loadSavedMap('esql example'); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + + it('should display ES|QL statement results on map', async () => { + const tooltipText = await PageObjects.maps.getLayerTocTooltipMsg('logstash-*'); + expect(tooltipText).to.equal( + 'logstash-*\nFound 5 rows.\nResults narrowed by global time\nResults narrowed by visible map area' + ); + }); + }); +} diff --git a/x-pack/test/functional/apps/maps/group1/index.js b/x-pack/test/functional/apps/maps/group1/index.js index a22ad70f7db4c4..50d3b74a0adf26 100644 --- a/x-pack/test/functional/apps/maps/group1/index.js +++ b/x-pack/test/functional/apps/maps/group1/index.js @@ -58,6 +58,7 @@ export default function ({ loadTestFile, getService }) { ); }); + loadTestFile(require.resolve('./esql_source')); loadTestFile(require.resolve('./documents_source')); loadTestFile(require.resolve('./blended_vector_layer')); loadTestFile(require.resolve('./saved_object_management')); diff --git a/x-pack/test/functional/fixtures/kbn_archiver/maps.json b/x-pack/test/functional/fixtures/kbn_archiver/maps.json index 832edf4cb705b4..69d44061692b33 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/maps.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/maps.json @@ -1168,3 +1168,25 @@ "updated_at": "2022-06-08T18:03:37.060Z", "version": "WzE0MSwxXQ==" } + +{ + "id": "f3bb9828-ad65-4feb-87d4-7a9f7deff8d5", + "type": "map", + "namespaces": [ + "default" + ], + "updated_at": "2023-12-17T15:28:47.759Z", + "created_at": "2023-12-17T15:28:47.759Z", + "version": "WzU0LDFd", + "attributes": { + "title": "esql example", + "description": "", + "mapStateJSON": "{\"adHocDataViews\":[],\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "layerListJSON": "[{\"sourceDescriptor\":{\"columns\":[{\"name\":\"geo.coordinates\",\"type\":\"geo_point\"}],\"dateField\":\"@timestamp\",\"esql\":\"from logstash-* | KEEP geo.coordinates | limit 10000\",\"id\":\"fad0e2eb-9278-415c-bdc8-1189a46eac0b\",\"type\":\"ESQL\",\"narrowByGlobalSearch\":true,\"narrowByMapBounds\":true,\"applyForceRefresh\":true},\"id\":\"59ca05b3-e3be-4fb4-ab4d-56c17b8bd589\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#54B399\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelZoomRange\":{\"options\":{\"useLayerZoomRange\":true,\"minZoom\":0,\"maxZoom\":24}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}},\"labelPosition\":{\"options\":{\"position\":\"CENTER\"}}},\"isTimeAware\":true},\"includeInFitToBounds\":true,\"type\":\"GEOJSON_VECTOR\",\"joins\":[],\"disableTooltips\":false}]", + "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "references": [], + "managed": false, + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "8.4.0" +} diff --git a/yarn.lock b/yarn.lock index 340ae8c920995f..a13c7bdf2b847b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13565,6 +13565,15 @@ concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@~1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +concat-stream@~1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" + integrity sha1-cIl4Yk2FavQaWnQd790mHadSwmY= + dependencies: + inherits "~2.0.1" + readable-stream "~2.0.0" + typedarray "~0.0.5" + concaveman@*: version "1.2.0" resolved "https://registry.yarnpkg.com/concaveman/-/concaveman-1.2.0.tgz#4340f27c08a11bdc1d5fac13476862a2ab09b703" @@ -22286,7 +22295,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8, minimist@~1.2.5: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8, minimist@~1.2.0, minimist@~1.2.5: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -24806,6 +24815,11 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + process-on-spawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" @@ -25998,6 +26012,18 @@ readable-stream@^4.0.0: events "^3.3.0" process "^0.11.10" +readable-stream@~2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdir-glob@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4" @@ -29393,7 +29419,7 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typedarray@^0.0.6: +typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= @@ -30870,6 +30896,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +wellknown@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/wellknown/-/wellknown-0.5.0.tgz#09ae9871fa826cf0a6ec1537ef00c379d78d7101" + integrity sha1-Ca6YcfqCbPCm7BU37wDDedeNcQE= + dependencies: + concat-stream "~1.5.0" + minimist "~1.2.0" + wgs84@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/wgs84/-/wgs84-0.0.0.tgz#34fdc555917b6e57cf2a282ed043710c049cdc76" From c706de0078b7475fe931ac9d30aaf3de3a49a5ca Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Wed, 3 Jan 2024 11:08:58 -0500 Subject: [PATCH 08/18] [ML][Enterprise Search] Reword E5 model description (#174171) ## Summary Adding "third party" to E5 ML model description as per legal requirements. Screenshot 2024-01-03 at 09 11 25 Screenshot 2024-01-03 at 09 12 04 ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) --- x-pack/plugins/enterprise_search/server/lib/ml/utils.ts | 2 +- .../ml/public/application/model_management/add_model_flyout.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts b/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts index 5d31216b22e227..19a43059d3a086 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/utils.ts @@ -87,7 +87,7 @@ export const E5_MODEL_PLACEHOLDER: MlModel = { title: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', description: i18n.translate('xpack.enterpriseSearch.modelCard.e5Placeholder.description', { defaultMessage: - 'E5 is an NLP model that enables you to perform multi-lingual semantic search by using dense vector representations. This model performs best for non-English language documents and queries.', + 'E5 is a third party NLP model that enables you to perform multi-lingual semantic search by using dense vector representations. This model performs best for non-English language documents and queries.', }), licenseType: 'mit', modelDetailsPageUrl: 'https://ela.st/multilingual-e5-small', diff --git a/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx b/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx index cf4efb4846fc04..cb908b8a523085 100644 --- a/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx +++ b/x-pack/plugins/ml/public/application/model_management/add_model_flyout.tsx @@ -212,7 +212,7 @@ const ClickToDownloadTabContent: FC = ({

From 4ff1f5941f163142df29b1827a858996d743a431 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Wed, 3 Jan 2024 08:16:43 -0800 Subject: [PATCH 09/18] [DOCS] Warn against using ES|QL query rules on production environments (#174130) --- docs/user/alerting/rule-types/es-query.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/alerting/rule-types/es-query.asciidoc b/docs/user/alerting/rule-types/es-query.asciidoc index a95403f2a03299..6aabe3a2c0ff79 100644 --- a/docs/user/alerting/rule-types/es-query.asciidoc +++ b/docs/user/alerting/rule-types/es-query.asciidoc @@ -44,7 +44,7 @@ For example: If you use {kibana-ref}/kuery-query.html[KQL] or {kibana-ref}/lucene-query.html[Lucene], you must specify a data view then define a text-based query. For example, `http.request.referrer: "https://example.com"`. -preview:[] If you use {ref}/esql.html[ES|QL], you must provide a source command followed by an optional series of processing commands, separated by pipe characters (|). +preview:["Do not use {esql} on production environments. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."] If you use {ref}/esql.html[ES|QL], you must provide a source command followed by an optional series of processing commands, separated by pipe characters (|). For example: [source,sh] From fb6b3e807045cf2de762c626e04d334fd6742b19 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 3 Jan 2024 11:24:51 -0500 Subject: [PATCH 10/18] =?UTF-8?q?Upgrade=20lmdb@2.6.9=E2=86=922.9.2=20(#17?= =?UTF-8?q?4108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Upgrades `lmdb` from v2.6.9 to v2.9.2, and `msgpackr` from v1.7.2 to v1.10.1. --- package.json | 2 +- yarn.lock | 183 ++++++++++++++++++++++++++------------------------- 2 files changed, 96 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index eb171c92f1b5c5..4f6aef817d5798 100644 --- a/package.json +++ b/package.json @@ -1583,7 +1583,7 @@ "jsondiffpatch": "0.4.1", "license-checker": "^25.0.1", "listr": "^0.14.1", - "lmdb": "^2.6.9", + "lmdb": "^2.9.2", "loader-utils": "^2.0.4", "marge": "^1.0.1", "micromatch": "^4.0.5", diff --git a/yarn.lock b/yarn.lock index a13c7bdf2b847b..ad697a39b4b23d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6519,35 +6519,35 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@lmdb/lmdb-darwin-arm64@2.6.9": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.6.9.tgz#4b84bb0ad71e78472332920c9cf8603ea3dad0bc" - integrity sha512-QxyheKfTP9k5ZVAiddCqGUtp2AD3/BMgYfski96iIbFH0skPFO+MYARMGZuemTgyM9uieT+5oKj4FjigWplpWg== +"@lmdb/lmdb-darwin-arm64@2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.9.2.tgz#da42cda48018eabfd678b9698e68a0102221b4ba" + integrity sha512-+GX51Fi8nZOrEXCFiQHnrCpKAzkfDA2sY5+M6Ry4wZEu711o2qlvg+7xXP+j7OT7+JsfB9ayGCdhra2AAaX02g== -"@lmdb/lmdb-darwin-x64@2.6.9": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.6.9.tgz#28b191a9f7a1f30462d8d179cd05598fa66ebbfc" - integrity sha512-zJ1oUepZMaqiujvWeWJRG5VHXBS3opJnjAzbd4vTVsQFT0t5rbPhHgAJ2ruR9rVrb2V1BINJZuwpjhIOg9fLCQ== +"@lmdb/lmdb-darwin-x64@2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.9.2.tgz#fb24813693175a858727d61b3dc33c277a739987" + integrity sha512-ajkq2oZTd/RXXpgaZqVm6LHoJYf4A42q+S+U4gYKRYpeR4ERGvG+VGCK9bi9MXInQfeq0KM1yv6rsYpvCOoNhQ== -"@lmdb/lmdb-linux-arm64@2.6.9": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.6.9.tgz#274dfe11209a70c059cb55c72026c24903dde3e1" - integrity sha512-KZRet8POwKowbYZqrRqdYJ+B6l+7cWG18vMCe2sgOSuE41sEMpfRQ1mKcolt3fsr0KVbuP63aPG+dwi0wGpX9w== +"@lmdb/lmdb-linux-arm64@2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.9.2.tgz#1fc10dfd165b7199b47c41ce9de99b22f46a8da4" + integrity sha512-WqQqWwFyL8JPVpKJyKnyyg7tnsVlD08PHEyxSMxDQC2EkPpvZuUz2oMqasDoy5tmYB0jANOI13/Qz3Mbh9endQ== -"@lmdb/lmdb-linux-arm@2.6.9": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.6.9.tgz#7bacd104067e7dbb1bb67c907c1bc642e2d2ac96" - integrity sha512-Umw+ikxbsYZHHqr8eMycmApj6IIZCK4k1rp5/pqqx9FvAaPv4/Y63owiMLoKfipjel0YPaNyvSeXAJK3l/8Pbw== +"@lmdb/lmdb-linux-arm@2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.9.2.tgz#2d4203c85e895cb75ffd6488791cc18de871a6b2" + integrity sha512-AAdmxDIh1tMYzXOUuDP+TNhvl9pLgvS63M6xhwgVArr79As4msraUSjIJ8J0jlhFKsN7nVoXzPB/jvpp8aK49w== -"@lmdb/lmdb-linux-x64@2.6.9": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.6.9.tgz#d37b25c9b553c5d5e66055a64d118e3fd42557d9" - integrity sha512-11xFQ4kCIPGnYULcfkW4SIMIY1sukA4DHez62DKvYn+tLr4AB1o9jm1Jk6bisKFh5Cql+JUr7klHxeIuxvGZdg== +"@lmdb/lmdb-linux-x64@2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.9.2.tgz#dc0f71c092c005b2ad9ddd3502151ecce7692702" + integrity sha512-rB4tE80EOxXwTJr9rsATWZghOVP8+mV085P5u/dBdttJSq3TLxY0CMZ8NKB/WJpryNnsfCI4OvjOAibF/fg+GQ== -"@lmdb/lmdb-win32-x64@2.6.9": - version "2.6.9" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.6.9.tgz#bf8e647dabd8b672744315f5df3e363b5987a463" - integrity sha512-qECZ+1j3PSarYeCmJlYlrxq3TB7S020ICrYmpxyQyphbRiMI9I1Bw4t+vPrMAEKsTqB8UaOzBp21YWUpsiCBfA== +"@lmdb/lmdb-win32-x64@2.9.2": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.9.2.tgz#84f561d05329c671f6e4119b483ce54410772bb6" + integrity sha512-VRrM/Zq/k8YEZlGuDvFi3NU753cm+vOa1kUcq4iNyeAEVXzjrSg5K3sHI0d6Od5gLsKctjlQeaFn6+21inU4bw== "@loaders.gl/core@^3.4.7": version "3.4.7" @@ -6818,35 +6818,35 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@msgpackr-extract/msgpackr-extract-darwin-arm64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.2.0.tgz#901c5937e1441572ea23e631fe6deca68482fe76" - integrity sha512-Z9LFPzfoJi4mflGWV+rv7o7ZbMU5oAU9VmzCgL240KnqDW65Y2HFCT3MW06/ITJSnbVLacmcEJA8phywK7JinQ== +"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz#44d752c1a2dc113f15f781b7cc4f53a307e3fa38" + integrity sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ== -"@msgpackr-extract/msgpackr-extract-darwin-x64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.2.0.tgz#fb877fe6bae3c4d3cea29786737840e2ae689066" - integrity sha512-vq0tT8sjZsy4JdSqmadWVw6f66UXqUCabLmUVHZwUFzMgtgoIIQjT4VVRHKvlof3P/dMCkbMJ5hB1oJ9OWHaaw== +"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz#f954f34355712212a8e06c465bc06c40852c6bb3" + integrity sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw== -"@msgpackr-extract/msgpackr-extract-linux-arm64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-2.2.0.tgz#986179c38b10ac41fbdaf7d036c825cbc72855d9" - integrity sha512-hlxxLdRmPyq16QCutUtP8Tm6RDWcyaLsRssaHROatgnkOxdleMTgetf9JsdncL8vLh7FVy/RN9i3XR5dnb9cRA== +"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz#45c63037f045c2b15c44f80f0393fa24f9655367" + integrity sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg== -"@msgpackr-extract/msgpackr-extract-linux-arm@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-2.2.0.tgz#15f2c6fe9e0adc06c21af7e95f484ff4880d79ce" - integrity sha512-SaJ3Qq4lX9Syd2xEo9u3qPxi/OB+5JO/ngJKK97XDpa1C587H9EWYO6KD8995DAjSinWvdHKRrCOXVUC5fvGOg== +"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz#35707efeafe6d22b3f373caf9e8775e8920d1399" + integrity sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA== -"@msgpackr-extract/msgpackr-extract-linux-x64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-2.2.0.tgz#30cae5c9a202f3e1fa1deb3191b18ffcb2f239a2" - integrity sha512-94y5PJrSOqUNcFKmOl7z319FelCLAE0rz/jPCWS+UtdMZvpa4jrQd+cJPQCLp2Fes1yAW/YUQj/Di6YVT3c3Iw== +"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz#091b1218b66c341f532611477ef89e83f25fae4f" + integrity sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA== -"@msgpackr-extract/msgpackr-extract-win32-x64@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.2.0.tgz#016d855b6bc459fd908095811f6826e45dd4ba64" - integrity sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA== +"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407" + integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ== "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" @@ -21174,23 +21174,23 @@ listr@^0.14.1: p-map "^2.0.0" rxjs "^6.3.3" -lmdb@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.6.9.tgz#aa782ec873bcf70333b251eede9e711819ef5765" - integrity sha512-rVA3OchNoKxoD2rYhtc9nooqbJmId+vvfPzTWhanRPhdVr0hbgnF9uB9ZEHFU2lEeYVdh83Pt2H6DudeWuz+JA== +lmdb@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.9.2.tgz#a67ed24cad282ba7ad21daf2a8a13c08dcb33f56" + integrity sha512-Q5SQzu4u4sdz4U8QT1uCS04beS7hS/1YYb1suJwaijqVETGAkrPBKr0ERxTeza/u2F6ei5+8UTnzm4ae3PJG3w== dependencies: - msgpackr "1.7.2" - node-addon-api "^4.3.0" - node-gyp-build-optional-packages "5.0.3" - ordered-binary "^1.4.0" + msgpackr "^1.9.9" + node-addon-api "^6.1.0" + node-gyp-build-optional-packages "5.1.1" + ordered-binary "^1.4.1" weak-lru-cache "^1.2.2" optionalDependencies: - "@lmdb/lmdb-darwin-arm64" "2.6.9" - "@lmdb/lmdb-darwin-x64" "2.6.9" - "@lmdb/lmdb-linux-arm" "2.6.9" - "@lmdb/lmdb-linux-arm64" "2.6.9" - "@lmdb/lmdb-linux-x64" "2.6.9" - "@lmdb/lmdb-win32-x64" "2.6.9" + "@lmdb/lmdb-darwin-arm64" "2.9.2" + "@lmdb/lmdb-darwin-x64" "2.9.2" + "@lmdb/lmdb-linux-arm" "2.9.2" + "@lmdb/lmdb-linux-arm64" "2.9.2" + "@lmdb/lmdb-linux-x64" "2.9.2" + "@lmdb/lmdb-win32-x64" "2.9.2" load-json-file@^1.0.0: version "1.1.0" @@ -22673,26 +22673,26 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msgpackr-extract@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-2.2.0.tgz#4bb749b58d9764cfdc0d91c7977a007b08e8f262" - integrity sha512-0YcvWSv7ZOGl9Od6Y5iJ3XnPww8O7WLcpYMDwX+PAA/uXLDtyw94PJv9GLQV/nnp3cWlDhMoyKZIQLrx33sWog== +msgpackr-extract@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz#e05ec1bb4453ddf020551bcd5daaf0092a2c279d" + integrity sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A== dependencies: - node-gyp-build-optional-packages "5.0.3" + node-gyp-build-optional-packages "5.0.7" optionalDependencies: - "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-linux-arm" "2.2.0" - "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-linux-x64" "2.2.0" - "@msgpackr-extract/msgpackr-extract-win32-x64" "2.2.0" - -msgpackr@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.7.2.tgz#68d6debf5999d6b61abb6e7046a689991ebf7261" - integrity sha512-mWScyHTtG6TjivXX9vfIy2nBtRupaiAj0HQ2mtmpmYujAmqZmaaEVPaSZ1NKLMvicaMLFzEaMk0ManxMRg8rMQ== + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.2" + +msgpackr@^1.9.9: + version "1.10.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.10.1.tgz#51953bb4ce4f3494f0c4af3f484f01cfbb306555" + integrity sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ== optionalDependencies: - msgpackr-extract "^2.1.2" + msgpackr-extract "^3.0.2" multicast-dns@^7.2.5: version "7.2.5" @@ -22903,11 +22903,6 @@ node-addon-api@^3.0.0, node-addon-api@^3.2.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-addon-api@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" - integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== - node-addon-api@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" @@ -22965,6 +22960,18 @@ node-gyp-build-optional-packages@5.0.3: resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== +node-gyp-build-optional-packages@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz#5d2632bbde0ab2f6e22f1bbac2199b07244ae0b3" + integrity sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w== + +node-gyp-build-optional-packages@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz#52b143b9dd77b7669073cbfe39e3f4118bfc603c" + integrity sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw== + dependencies: + detect-libc "^2.0.1" + node-gyp-build@^4.2.2, node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" @@ -23559,10 +23566,10 @@ ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" -ordered-binary@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.4.0.tgz#6bb53d44925f3b8afc33d1eed0fa15693b211389" - integrity sha512-EHQ/jk4/a9hLupIKxTfUsQRej1Yd/0QLQs3vGvIqg5ZtCYSzNhkzHoZc7Zf4e4kUlDaC3Uw8Q/1opOLNN2OKRQ== +ordered-binary@^1.4.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.5.1.tgz#94ccbf14181711081ee23931db0dc3f58aaa0df6" + integrity sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A== original-url@^1.2.3: version "1.2.3" From 7c2b3f1301803bec477af3f1e9657b8c2821619d Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 3 Jan 2024 10:29:14 -0600 Subject: [PATCH 11/18] [build] Remove ubi8 (#173873) RHEL 8.10 will be the final ubi8 release. We already have builds in place for transitioning to ubi9. Depends on https://github.com/elastic/kibana/pull/170264. --- .../scripts/steps/artifacts/docker_context.sh | 4 ---- .../tasks/os_packages/create_os_package_tasks.ts | 15 ++------------- .../tasks/os_packages/docker_generator/run.ts | 8 +++----- .../docker_generator/template_context.ts | 2 +- .../templates/dockerfile.template.ts | 4 ++-- 5 files changed, 8 insertions(+), 25 deletions(-) diff --git a/.buildkite/scripts/steps/artifacts/docker_context.sh b/.buildkite/scripts/steps/artifacts/docker_context.sh index de90621ada2d9f..ad09e00124ab13 100755 --- a/.buildkite/scripts/steps/artifacts/docker_context.sh +++ b/.buildkite/scripts/steps/artifacts/docker_context.sh @@ -23,11 +23,7 @@ case $KIBANA_DOCKER_CONTEXT in cloud) DOCKER_CONTEXT_FILE="kibana-cloud-$FULL_VERSION-docker-build-context.tar.gz" ;; - ubi8) - DOCKER_CONTEXT_FILE="kibana-ubi8-$FULL_VERSION-docker-build-context.tar.gz" - ;; ubi) - # Currently ubi9. After ubi8 we're moving to a version agnostic filename DOCKER_CONTEXT_FILE="kibana-ubi-$FULL_VERSION-docker-build-context.tar.gz" ;; ironbank) diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 8563c19f9d5386..7cb9697364c757 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -109,13 +109,7 @@ export const CreateDockerUBI: Task = { async run(config, log, build) { await runDockerGenerator(config, log, build, { architecture: 'x64', - baseImage: 'ubi8', - context: false, - image: true, - }); - await runDockerGenerator(config, log, build, { - architecture: 'x64', - baseImage: 'ubi9', + baseImage: 'ubi', context: false, image: true, }); @@ -154,12 +148,7 @@ export const CreateDockerContexts: Task = { dockerBuildDate, }); await runDockerGenerator(config, log, build, { - baseImage: 'ubi8', - context: true, - image: false, - }); - await runDockerGenerator(config, log, build, { - baseImage: 'ubi9', + baseImage: 'ubi', context: true, image: false, }); diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index eb7a03a9e933f1..16c48ad4921875 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -29,7 +29,7 @@ export async function runDockerGenerator( build: Build, flags: { architecture?: string; - baseImage: 'none' | 'ubi9' | 'ubi8' | 'ubuntu'; + baseImage: 'none' | 'ubi' | 'ubuntu'; context: boolean; image: boolean; ironbank?: boolean; @@ -40,12 +40,10 @@ export async function runDockerGenerator( ) { let baseImageName = ''; if (flags.baseImage === 'ubuntu') baseImageName = 'ubuntu:20.04'; - if (flags.baseImage === 'ubi8') baseImageName = 'docker.elastic.co/ubi8/ubi-minimal:latest'; - if (flags.baseImage === 'ubi9') baseImageName = 'docker.elastic.co/ubi9/ubi-minimal:latest'; + if (flags.baseImage === 'ubi') baseImageName = 'docker.elastic.co/ubi9/ubi-minimal:latest'; let imageFlavor = ''; - if (flags.baseImage === 'ubi8') imageFlavor += `-ubi8`; - if (flags.baseImage === 'ubi9') imageFlavor += `-ubi`; + if (flags.baseImage === 'ubi') imageFlavor += `-ubi`; if (flags.ironbank) imageFlavor += '-ironbank'; if (flags.cloud) imageFlavor += '-cloud'; if (flags.serverless) imageFlavor += '-serverless'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts index 61609203edcc00..edd0aed87e2814 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts @@ -24,7 +24,7 @@ export interface TemplateContext { dockerBuildDate: string; usePublicArtifact?: boolean; publicArtifactSubdomain: string; - baseImage: 'none' | 'ubi8' | 'ubi9' | 'ubuntu'; + baseImage: 'none' | 'ubi' | 'ubuntu'; baseImageName: string; cloud?: boolean; serverless?: boolean; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts index 456a09ccc3db3c..57fc4d93a760aa 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts @@ -16,8 +16,8 @@ function generator(options: TemplateContext) { const dir = options.ironbank ? 'ironbank' : 'base'; const template = readFileSync(resolve(__dirname, dir, './Dockerfile')); return Mustache.render(template.toString(), { - packageManager: options.baseImage.includes('ubi') ? 'microdnf' : 'apt-get', - ubi: options.baseImage.includes('ubi'), + packageManager: options.baseImage === 'ubi' ? 'microdnf' : 'apt-get', + ubi: options.baseImage === 'ubi', ubuntu: options.baseImage === 'ubuntu', opensslLegacyProvider: !(options.cloud || options.serverless), ...options, From 9dc9d8ff8ffbb9d4be85a69e4de1c3a082c115ed Mon Sep 17 00:00:00 2001 From: Shahzad Date: Wed, 3 Jan 2024 17:40:17 +0100 Subject: [PATCH 12/18] [SLOs] Configuration inspect api and flyout (#173723) ## Summary It will show all the associated configs at one place in json form, configuration, ingest pipeline config, roll up transform and summary transform config !! Motivation is to understand things while onboarding devs to slo and during normal development. https://github.com/elastic/kibana/assets/3505601/a22ad292-ba59-4145-989e-80803b6a1e3e --- .../kbn-slo-schema/src/rest_specs/slo.ts | 1 + .../plugins/ingest_pipelines/public/index.ts | 3 + x-pack/plugins/observability/kibana.jsonc | 3 +- .../public/application/index.tsx | 1 + .../context/plugin_context/plugin_context.tsx | 1 + .../public/hooks/slo/use_inspect_slo.ts | 41 +++ .../components/common/inspect_slo_portal.tsx | 26 ++ .../components/common/slo_inspect.tsx | 270 ++++++++++++++++++ .../slo_edit/components/slo_edit_form.tsx | 4 +- .../public/pages/slo_edit/slo_edit.tsx | 5 +- .../observability/server/routes/slo/route.ts | 37 +++ .../server/services/slo/create_slo.ts | 29 +- .../server/services/slo/mocks/index.ts | 2 + .../services/slo/summay_transform_manager.ts | 5 + .../server/services/slo/transform_manager.ts | 12 + x-pack/plugins/observability/tsconfig.json | 1 + 16 files changed, 437 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/slo/use_inspect_slo.ts create mode 100644 x-pack/plugins/observability/public/pages/slo_edit/components/common/inspect_slo_portal.tsx create mode 100644 x-pack/plugins/observability/public/pages/slo_edit/components/common/slo_inspect.tsx diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts index 574a7eb1f9244f..38233d2982c08f 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -50,6 +50,7 @@ const createSLOParamsSchema = t.type({ settings: optionalSettingsSchema, tags: tagsSchema, groupBy: allOrAnyString, + revision: t.number, }), ]), }); diff --git a/x-pack/plugins/ingest_pipelines/public/index.ts b/x-pack/plugins/ingest_pipelines/public/index.ts index d120f60ef8a2d1..b269245faf520c 100644 --- a/x-pack/plugins/ingest_pipelines/public/index.ts +++ b/x-pack/plugins/ingest_pipelines/public/index.ts @@ -10,3 +10,6 @@ import { IngestPipelinesPlugin } from './plugin'; export function plugin() { return new IngestPipelinesPlugin(); } + +export { INGEST_PIPELINES_APP_LOCATOR, INGEST_PIPELINES_PAGES } from './locator'; +export type { IngestPipelinesListParams } from './locator'; diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index d5633ad9f36feb..526c283c0f0bec 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -57,7 +57,8 @@ "unifiedSearch", "stackAlerts", "spaces", - "embeddable" + "embeddable", + "ingestPipelines" ], "extraPublicDirs": [ "common" diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 23a0952ed91db4..1166755ca14572 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -104,6 +104,7 @@ export const renderApp = ({ > ; + +interface SLOInspectResponse { + slo: SLOResponse; + pipeline: Record; + rollUpTransform: TransformPutTransformRequest; + summaryTransform: TransformPutTransformRequest; + temporaryDoc: Record; +} + +export function useInspectSlo() { + const { http } = useKibana().services; + + return useMutation< + SLOInspectResponse, + ServerError, + { slo: CreateSLOInput }, + { previousData?: FindSLOResponse; queryKey?: QueryKey } + >( + ['inspectSlo'], + ({ slo }) => { + const body = JSON.stringify(slo); + return http.post(`/internal/api/observability/slos/_inspect`, { body }); + }, + {} + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/inspect_slo_portal.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/inspect_slo_portal.tsx new file mode 100644 index 00000000000000..d03772c9bf4ce7 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/common/inspect_slo_portal.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { InPortal } from 'react-reverse-portal'; +import { GetSLOResponse } from '@kbn/slo-schema'; +import { CreateSLOForm } from '../../types'; +import { SLOInspectWrapper } from './slo_inspect'; +import { InspectSLOPortalNode } from '../../slo_edit'; + +export interface SloInspectPortalProps { + getValues: () => CreateSLOForm; + trigger: () => Promise; + slo?: GetSLOResponse; +} +export function InspectSLOPortal(props: SloInspectPortalProps) { + return ( + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/slo_inspect.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/slo_inspect.tsx new file mode 100644 index 00000000000000..2c96c0d2d05bce --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/common/slo_inspect.tsx @@ -0,0 +1,270 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +import React, { ReactNode, useState } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useFetcher } from '@kbn/observability-shared-plugin/public'; +import { + EuiFlyout, + EuiButton, + EuiCodeBlock, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutFooter, + EuiSpacer, + EuiFlyoutBody, + EuiToolTip, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiAccordion, + EuiButtonIcon, +} from '@elastic/eui'; +import { + INGEST_PIPELINES_APP_LOCATOR, + INGEST_PIPELINES_PAGES, + IngestPipelinesListParams, +} from '@kbn/ingest-pipelines-plugin/public'; +import { SloInspectPortalProps } from './inspect_slo_portal'; +import { ObservabilityPublicPluginsStart } from '../../../..'; +import { useInspectSlo } from '../../../../hooks/slo/use_inspect_slo'; +import { transformCreateSLOFormToCreateSLOInput } from '../../helpers/process_slo_form_values'; +import { enableInspectEsQueries } from '../../../../../common'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; + +export function SLOInspectWrapper(props: SloInspectPortalProps) { + const { + services: { uiSettings }, + } = useKibana(); + + const { isDev } = usePluginContext(); + + const isInspectorEnabled = uiSettings?.get(enableInspectEsQueries); + + return isDev || isInspectorEnabled ? : null; +} + +function SLOInspect({ getValues, trigger, slo }: SloInspectPortalProps) { + const { share, http } = useKibana().services; + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const { mutateAsync: inspectSlo, data, isLoading } = useInspectSlo(); + + const { data: sloData } = useFetcher(async () => { + if (!isFlyoutVisible) { + return; + } + const isValid = await trigger(); + if (!isValid) { + return; + } + const sloForm = transformCreateSLOFormToCreateSLOInput(getValues()); + inspectSlo({ slo: { ...sloForm, id: slo?.id, revision: slo?.revision } }); + return sloForm; + }, [isFlyoutVisible, trigger, getValues, inspectSlo, slo]); + + const { data: pipeLineUrl } = useFetcher(async () => { + const ingestPipeLocator = share.url.locators.get( + INGEST_PIPELINES_APP_LOCATOR + ); + const ingestPipeLineId = data?.pipeline?.id; + return ingestPipeLocator?.getUrl({ + pipelineId: ingestPipeLineId, + page: INGEST_PIPELINES_PAGES.LIST, + }); + }, [data?.pipeline?.id, share.url.locators]); + + const closeFlyout = () => { + setIsFlyoutVisible(false); + setIsInspecting(false); + }; + + const [isInspecting, setIsInspecting] = useState(false); + const onButtonClick = () => { + trigger().then((isValid) => { + if (isValid) { + setIsInspecting(() => !isInspecting); + setIsFlyoutVisible(() => !isFlyoutVisible); + } + }); + }; + + let flyout; + + if (isFlyoutVisible) { + flyout = ( + + + +

{CONFIG_LABEL}

+
+
+ + {isLoading && } + + {data && ( + <> + + + + } + /> + + + + } + /> + + + + } + json={data.pipeline} + /> + + + + + )} + + + + {i18n.translate('xpack.observability.sLOInspect.closeButtonLabel', { + defaultMessage: 'Close', + })} + + +
+ ); + } + return ( + <> + + + {SLO_INSPECT_LABEL} + + + + {flyout} + + ); +} + +function CodeBlockAccordion({ + id, + label, + json, + extraAction, +}: { + id: string; + label: string; + json: any; + extraAction?: ReactNode; +}) { + return ( + +

{label}

+ + } + > + + {JSON.stringify(json, null, 2)} + +
+ ); +} + +export function LoadingState() { + return ( + + + + + + ); +} + +const SLO_INSPECT_LABEL = i18n.translate('xpack.observability.sLOInspect.sLOInspectButtonLabel', { + defaultMessage: 'SLO Inspect', +}); + +const VIEW_FORMATTED_CONFIG_LABEL = i18n.translate( + 'xpack.observability.slo.viewFormattedResourcesConfigsButtonLabel', + { defaultMessage: 'View formatted resources configs for SLO' } +); + +const VALID_CONFIG_LABEL = i18n.translate('xpack.observability.slo.formattedConfigLabel.valid', { + defaultMessage: 'Only valid form configurations can be inspected.', +}); + +const CONFIG_LABEL = i18n.translate('xpack.observability.monitorInspect.configLabel', { + defaultMessage: 'SLO Configurations', +}); diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx index 0e3c491bdd7c2f..bf325351182939 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx @@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n'; import type { GetSLOResponse } from '@kbn/slo-schema'; import React, { useCallback, useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; +import { InspectSLOPortal } from './common/inspect_slo_portal'; import { EquivalentApiRequest } from './common/equivalent_api_request'; import { BurnRateRuleFlyout } from '../../slos/components/common/burn_rate_rule_flyout'; import { paths } from '../../../../common/locators/paths'; @@ -191,7 +192,7 @@ export function SloEditForm({ slo }: Props) { defaultMessage: 'SLO burn rate alert rule', })} - {' '} + + ], + rightSideItems: [, ], bottomBorder: false, }} data-test-subj="slosEditPage" diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 7ad4b7c36dcc7f..0ce51e284e949e 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -114,6 +114,42 @@ const createSLORoute = createObservabilityServerRoute({ }, }); +const inspectSLORoute = createObservabilityServerRoute({ + endpoint: 'POST /internal/api/observability/slos/_inspect 2023-10-31', + options: { + tags: ['access:slo_write'], + access: 'public', + }, + params: createSLOParamsSchema, + handler: async ({ context, params, logger, dependencies, request }) => { + await assertPlatinumLicense(context); + + const spaceId = + (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; + + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + const repository = new KibanaSavedObjectsSLORepository(soClient); + const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger); + const summaryTransformManager = new DefaultSummaryTransformManager( + new DefaultSummaryTransformGenerator(), + esClient, + logger + ); + + const createSLO = new CreateSLO( + esClient, + repository, + transformManager, + summaryTransformManager, + logger, + spaceId + ); + + return createSLO.inspect(params.body); + }, +}); + const updateSLORoute = createObservabilityServerRoute({ endpoint: 'PUT /api/observability/slos/{id} 2023-10-31', options: { @@ -481,6 +517,7 @@ const getPreviewData = createObservabilityServerRoute({ export const sloRouteRepository = { ...createSLORoute, + ...inspectSLORoute, ...deleteSLORoute, ...deleteSloInstancesRoute, ...disableSLORoute, diff --git a/x-pack/plugins/observability/server/services/slo/create_slo.ts b/x-pack/plugins/observability/server/services/slo/create_slo.ts index d7e116d983584c..89e8c7165f3951 100644 --- a/x-pack/plugins/observability/server/services/slo/create_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/create_slo.ts @@ -8,6 +8,7 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { ALL_VALUE, CreateSLOParams, CreateSLOResponse } from '@kbn/slo-schema'; import { v4 as uuidv4 } from 'uuid'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { getSLOSummaryPipelineId, getSLOSummaryTransformId, @@ -83,6 +84,32 @@ export class CreateSLO { return this.toResponse(slo); } + public inspect(params: CreateSLOParams): { + slo: CreateSLOParams; + pipeline: Record; + rollUpTransform: TransformPutTransformRequest; + summaryTransform: TransformPutTransformRequest; + temporaryDoc: Record; + } { + const slo = this.toSLO(params); + validateSLO(slo); + + const rollUpTransform = this.transformManager.inspect(slo); + const pipeline = getSLOSummaryPipelineTemplate(slo, this.spaceId); + + const summaryTransform = this.summaryTransformManager.inspect(slo); + + const temporaryDoc = createTempSummaryDocument(slo, this.spaceId); + + return { + pipeline, + temporaryDoc, + summaryTransform, + rollUpTransform, + slo, + }; + } + private toSLO(params: CreateSLOParams): SLO { const now = new Date(); return { @@ -92,7 +119,7 @@ export class CreateSLO { syncDelay: params.settings?.syncDelay ?? new Duration(1, DurationUnit.Minute), frequency: params.settings?.frequency ?? new Duration(1, DurationUnit.Minute), }, - revision: 1, + revision: params.revision ?? 1, enabled: true, tags: params.tags ?? [], createdAt: now, diff --git a/x-pack/plugins/observability/server/services/slo/mocks/index.ts b/x-pack/plugins/observability/server/services/slo/mocks/index.ts index eb8db093a71740..d6c932fcbca001 100644 --- a/x-pack/plugins/observability/server/services/slo/mocks/index.ts +++ b/x-pack/plugins/observability/server/services/slo/mocks/index.ts @@ -25,6 +25,7 @@ const createTransformManagerMock = (): jest.Mocked => { uninstall: jest.fn(), start: jest.fn(), stop: jest.fn(), + inspect: jest.fn(), }; }; @@ -35,6 +36,7 @@ const createSummaryTransformManagerMock = (): jest.Mocked => { uninstall: jest.fn(), start: jest.fn(), stop: jest.fn(), + inspect: jest.fn(), }; }; diff --git a/x-pack/plugins/observability/server/services/slo/summay_transform_manager.ts b/x-pack/plugins/observability/server/services/slo/summay_transform_manager.ts index bc22f801c9fcc3..57349105e020ab 100644 --- a/x-pack/plugins/observability/server/services/slo/summay_transform_manager.ts +++ b/x-pack/plugins/observability/server/services/slo/summay_transform_manager.ts @@ -7,6 +7,7 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { SLO } from '../../domain/models'; import { SecurityException } from '../../errors'; import { retryTransientEsErrors } from '../../utils/retry'; @@ -40,6 +41,10 @@ export class DefaultSummaryTransformManager implements TransformManager { return transformParams.transform_id; } + inspect(slo: SLO): TransformPutTransformRequest { + return this.generator.generate(slo); + } + async preview(transformId: string): Promise { try { await retryTransientEsErrors( diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.ts index ed35512c03b65e..954c7f7d959120 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.ts @@ -7,6 +7,7 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { SLO, IndicatorTypes } from '../../domain/models'; import { SecurityException } from '../../errors'; import { retryTransientEsErrors } from '../../utils/retry'; @@ -16,6 +17,7 @@ type TransformId = string; export interface TransformManager { install(slo: SLO): Promise; + inspect(slo: SLO): TransformPutTransformRequest; preview(transformId: TransformId): Promise; start(transformId: TransformId): Promise; stop(transformId: TransformId): Promise; @@ -53,6 +55,16 @@ export class DefaultTransformManager implements TransformManager { return transformParams.transform_id; } + inspect(slo: SLO): TransformPutTransformRequest { + const generator = this.generators[slo.indicator.type]; + if (!generator) { + this.logger.error(`No transform generator found for indicator type [${slo.indicator.type}]`); + throw new Error(`Unsupported indicator type [${slo.indicator.type}]`); + } + + return generator.getTransformParams(slo); + } + async preview(transformId: string): Promise { try { await retryTransientEsErrors( diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 61762322f9eed0..cf3402a90c888c 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -100,6 +100,7 @@ "@kbn/presentation-util-plugin", "@kbn/task-manager-plugin", "@kbn/core-elasticsearch-client-server-mocks", + "@kbn/ingest-pipelines-plugin", "@kbn/core-saved-objects-api-server-mocks" ], "exclude": [ From e1601bef2480427b14dd7aaece58b005843297c8 Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Wed, 3 Jan 2024 10:42:36 -0600 Subject: [PATCH 13/18] [ML] Fix filter for boolean fields filtering for numbers in Field statistics/Data Visualizer (#174050) ## Summary This PR fixes https://github.com/elastic/kibana/issues/173734 where the clicking to filter a boolean value is searching as numeric representation instead of the true boolean values. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/field_request_config.ts | 7 +- .../components/top_values/top_values.tsx | 153 +++++++++--------- 2 files changed, 79 insertions(+), 81 deletions(-) diff --git a/x-pack/plugins/data_visualizer/common/types/field_request_config.ts b/x-pack/plugins/data_visualizer/common/types/field_request_config.ts index 34a9f5d6036f25..ee6a382c4fb95c 100644 --- a/x-pack/plugins/data_visualizer/common/types/field_request_config.ts +++ b/x-pack/plugins/data_visualizer/common/types/field_request_config.ts @@ -65,7 +65,12 @@ export interface FieldVisStats { max?: number; median?: number; min?: number; - topValues?: Array<{ key: number | string; doc_count: number; percent: number }>; + topValues?: Array<{ + key: number | string; + doc_count: number; + percent: number; + key_as_string?: string; + }>; examples?: Array; timeRangeEarliest?: number; timeRangeLatest?: number; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx index a3f5731dcb1bc0..0b2475789091f6 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx @@ -114,87 +114,80 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, className={classNames('fieldDataTopValuesContainer', 'dvTopValues__wrapper')} > {Array.isArray(topValues) - ? topValues.map((value) => ( - - - - - {fieldName !== undefined && value.key !== undefined && onAddFilter !== undefined ? ( -
- - onAddFilter( - fieldName, - typeof value.key === 'number' ? value.key.toString() : value.key, - '+' - ) - } - aria-label={i18n.translate( - 'xpack.dataVisualizer.dataGrid.field.addFilterAriaLabel', - { - defaultMessage: 'Filter for {fieldName}: "{value}"', - values: { fieldName, value: value.key }, - } - )} - data-test-subj={`dvFieldDataTopValuesAddFilterButton-${value.key}-${value.key}`} - style={{ - minHeight: 'auto', - minWidth: 'auto', - paddingRight: 2, - paddingLeft: 2, - paddingTop: 0, - paddingBottom: 0, - }} - /> - - onAddFilter( - fieldName, - typeof value.key === 'number' ? value.key.toString() : value.key, - '-' - ) - } - aria-label={i18n.translate( - 'xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel', - { - defaultMessage: 'Filter out {fieldName}: "{value}"', - values: { fieldName, value: value.key }, - } - )} - data-test-subj={`dvFieldDataTopValuesExcludeFilterButton-${value.key}-${value.key}`} - style={{ - minHeight: 'auto', - minWidth: 'auto', - paddingTop: 0, - paddingBottom: 0, - paddingRight: 2, - paddingLeft: 2, - }} + ? topValues.map((value) => { + const fieldValue = value.key_as_string ?? value.key.toString(); + return ( + + + -
- ) : null} -
- )) + + {fieldName !== undefined && + fieldValue !== undefined && + onAddFilter !== undefined ? ( +
+ onAddFilter(fieldName, fieldValue, '+')} + aria-label={i18n.translate( + 'xpack.dataVisualizer.dataGrid.field.addFilterAriaLabel', + { + defaultMessage: 'Filter for {fieldName}: "{value}"', + values: { fieldName, value: fieldValue }, + } + )} + data-test-subj={`dvFieldDataTopValuesAddFilterButton-${fieldName}-${fieldValue}`} + style={{ + minHeight: 'auto', + minWidth: 'auto', + paddingRight: 2, + paddingLeft: 2, + paddingTop: 0, + paddingBottom: 0, + }} + /> + onAddFilter(fieldName, fieldValue, '-')} + aria-label={i18n.translate( + 'xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel', + { + defaultMessage: 'Filter out {fieldName}: "{value}"', + values: { fieldName, value: fieldValue }, + } + )} + data-test-subj={`dvFieldDataTopValuesExcludeFilterButton-${fieldName}-${fieldValue}`} + style={{ + minHeight: 'auto', + minWidth: 'auto', + paddingTop: 0, + paddingBottom: 0, + paddingRight: 2, + paddingLeft: 2, + }} + /> +
+ ) : null} + + ); + }) : null} {topValuesOtherCount > 0 ? ( From 37ca617150080b1d407158dfb8ee670d77f2f33a Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Wed, 3 Jan 2024 16:43:04 +0000 Subject: [PATCH 14/18] [Logs Explorer] Add AI assistant to Logs Explorer (#174079) Resolves #172158 ## Summary - Adds AI assistant buttons to Logs Explorer header - Adds separators to group related buttons ## Screenshots ### Classic Screenshot 2023-12-20 at 17 45 15 ### Serverless Screenshot 2023-12-20 at 17 36 22 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/observability_log_explorer/kibana.jsonc | 7 +++++-- .../public/components/log_explorer_top_nav_menu.tsx | 12 +++++++++++- .../observability_log_explorer/public/types.ts | 2 ++ .../plugins/observability_log_explorer/tsconfig.json | 1 + 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_log_explorer/kibana.jsonc b/x-pack/plugins/observability_log_explorer/kibana.jsonc index 92d2ad70c3175f..d3d96863ac37ea 100644 --- a/x-pack/plugins/observability_log_explorer/kibana.jsonc +++ b/x-pack/plugins/observability_log_explorer/kibana.jsonc @@ -16,6 +16,7 @@ "discover", "logExplorer", "logsShared", + "observabilityAIAssistant", "observabilityShared", "share", "kibanaUtils", @@ -24,9 +25,11 @@ "optionalPlugins": [ "serverless" ], - "requiredBundles": ["kibanaReact"], + "requiredBundles": [ + "kibanaReact" + ], "extraPublicDirs": [ "common", ] } -} +} \ No newline at end of file diff --git a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx index 6a76caae254061..a9e1135cbb0992 100644 --- a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx +++ b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx @@ -37,6 +37,7 @@ export const LogExplorerTopNavMenu = () => { const ServerlessTopNav = () => { const { services } = useKibanaContextForPlugin(); + const { ObservabilityAIAssistantActionMenuItem } = services.observabilityAIAssistant; return ( @@ -63,9 +64,13 @@ const ServerlessTopNav = () => { + + + {ObservabilityAIAssistantActionMenuItem ? ( + + ) : null} - @@ -79,6 +84,7 @@ const StatefulTopNav = () => { const { services: { appParams: { setHeaderActionMenu }, + observabilityAIAssistant: { ObservabilityAIAssistantActionMenuItem }, chrome, i18n, theme, @@ -136,6 +142,10 @@ const StatefulTopNav = () => { + + {ObservabilityAIAssistantActionMenuItem ? ( + + ) : null} diff --git a/x-pack/plugins/observability_log_explorer/public/types.ts b/x-pack/plugins/observability_log_explorer/public/types.ts index 245e8227c72b06..a035d0af1a36d6 100644 --- a/x-pack/plugins/observability_log_explorer/public/types.ts +++ b/x-pack/plugins/observability_log_explorer/public/types.ts @@ -14,6 +14,7 @@ import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import { AppMountParameters, ScopedHistory } from '@kbn/core/public'; import { LogsSharedClientStartExports } from '@kbn/logs-shared-plugin/public'; import { DatasetQualityPluginStart } from '@kbn/dataset-quality-plugin/public'; +import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; import { ObservabilityLogExplorerLocators, ObservabilityLogExplorerLocationState, @@ -37,6 +38,7 @@ export interface ObservabilityLogExplorerStartDeps { discover: DiscoverStart; logExplorer: LogExplorerPluginStart; logsShared: LogsSharedClientStartExports; + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; observabilityShared: ObservabilitySharedPluginStart; serverless?: ServerlessPluginStart; share: SharePluginStart; diff --git a/x-pack/plugins/observability_log_explorer/tsconfig.json b/x-pack/plugins/observability_log_explorer/tsconfig.json index 0c03040b4203b7..67fb5561437719 100644 --- a/x-pack/plugins/observability_log_explorer/tsconfig.json +++ b/x-pack/plugins/observability_log_explorer/tsconfig.json @@ -37,6 +37,7 @@ "@kbn/ui-theme", "@kbn/xstate-utils", "@kbn/router-utils", + "@kbn/observability-ai-assistant-plugin", ], "exclude": [ "target/**/*" From 5951512197d736d575464c4c8839aa1993b878ed Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 3 Jan 2024 11:52:52 -0600 Subject: [PATCH 15/18] [ci] Avoid unintended notifications on failing test comment (#174124) --- .buildkite/pipeline-utils/test-failures/annotate.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.buildkite/pipeline-utils/test-failures/annotate.ts b/.buildkite/pipeline-utils/test-failures/annotate.ts index 8397fa670af668..c49128ae3c62b8 100644 --- a/.buildkite/pipeline-utils/test-failures/annotate.ts +++ b/.buildkite/pipeline-utils/test-failures/annotate.ts @@ -91,11 +91,12 @@ export const getPrComment = ( const logsLink = artifactUrl ? ` [[logs]](${artifactUrl})` : ''; - // job name could have # in it, which Github will link to an issue, so we need to "escape" it with spans - return `* [[job]](${jobUrl})${logsLink} ${failure.jobName.replace( - '#', - '#' - )} / ${failure.name}`; + // failure name could have #, which Github will link to an issue or @, + // which will send a notification so we need to "escape" it with spans + const failureString = `${failure.jobName} / ${failure.name}` + .replaceAll('#', '#') + .replaceAll('@', '@'); + return `* [[job]](${jobUrl})${logsLink} ${failureString}`; }) .join('\n') ); From 64d73d95a222a682a24ce3e4bd18b6a9697741c9 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 3 Jan 2024 11:53:15 -0600 Subject: [PATCH 16/18] Upgrade caniuse-lite (#173964) This is logging outdated warnings on 7.17. Opening with a main target to backport a consistent version. --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ad697a39b4b23d..28057e7c10d959 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12844,9 +12844,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001565: - version "1.0.30001570" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" - integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== + version "1.0.30001572" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz#1ccf7dc92d2ee2f92ed3a54e11b7b4a3041acfa0" + integrity sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw== canvg@^3.0.9: version "3.0.9" From 39f1561055d9e4e16ccc2389da2947c3e1f15c14 Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Thu, 4 Jan 2024 05:56:15 +1030 Subject: [PATCH 17/18] [main] Sync bundled packages with Package Storage (#174190) Automated by https://buildkite.com/elastic/package-storage-infra-kibana-discover-release-branches/builds/211 --- fleet_packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fleet_packages.json b/fleet_packages.json index 0a9a4546e87506..2041d127f916b9 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -56,6 +56,6 @@ }, { "name": "security_detection_engine", - "version": "8.12.1" + "version": "8.12.2" } ] \ No newline at end of file From c4821c5b00c021b0b22d697c93c26dcfebd33520 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:37:52 -0800 Subject: [PATCH 18/18] [ResponseOps] Revisit connector action retry back-off (#173779) Resolves https://github.com/elastic/kibana/issues/172518 ## Summary Updates the retry delay calculation to cap the delay at 1hr and introduces jitter. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### To verify - Create a rule and then force a retry failure - Verify that the retry follows the pattern below: Attempt 1: now Attempt 2: 30s after the first attempt Attempt 3: 0 - 5m after the second attempt Attempt 4: 0 - 10m after the third attempt Attempt 5: 0 - 20m after the fourth attempt Attempt 6: 0 - 40m after the fifth attempt Attempt n: 0 - 1hr for all other attempts --- .../server/task_running/task_runner.test.ts | 58 ++++++++++++++++--- .../server/task_running/task_runner.ts | 12 ++-- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index 35fbd6918f4fb4..8a96405abfed65 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -43,6 +43,7 @@ import { import { schema } from '@kbn/config-schema'; import { RequeueInvalidTasksConfig } from '../config'; +const baseDelay = 5 * 60 * 1000; const executionContext = executionContextServiceMock.createSetupContract(); const minutesFromNow = (mins: number): Date => secondsFromNow(mins * 60); const mockRequeueInvalidTasksConfig = { @@ -315,8 +316,16 @@ describe('TaskManagerRunner', () => { expect(instance.attempts).toEqual(initialAttempts + 1); expect(instance.status).toBe('running'); expect(instance.startedAt!.getTime()).toEqual(Date.now()); - const expectedRunAt = Date.now() + calculateDelay(initialAttempts + 1); - expect(instance.retryAt!.getTime()).toEqual(expectedRunAt + timeoutMinutes * 60 * 1000); + + const minRunAt = Date.now(); + const maxRunAt = minRunAt + baseDelay * Math.pow(2, initialAttempts - 1); + expect(instance.retryAt!.getTime()).toBeGreaterThanOrEqual( + minRunAt + timeoutMinutes * 60 * 1000 + ); + expect(instance.retryAt!.getTime()).toBeLessThanOrEqual( + maxRunAt + timeoutMinutes * 60 * 1000 + ); + expect(instance.enabled).not.toBeDefined(); }); @@ -347,9 +356,13 @@ describe('TaskManagerRunner', () => { expect(store.update).toHaveBeenCalledTimes(1); const instance = store.update.mock.calls[0][0]; - const expectedRetryAt = new Date(Date.now() + calculateDelay(initialAttempts + 1)); - expect(instance.retryAt!.getTime()).toEqual( - new Date(expectedRetryAt.getTime() + timeoutMinutes * 60 * 1000).getTime() + const minRunAt = Date.now(); + const maxRunAt = minRunAt + baseDelay * Math.pow(2, initialAttempts - 1); + expect(instance.retryAt!.getTime()).toBeGreaterThanOrEqual( + minRunAt + timeoutMinutes * 60 * 1000 + ); + expect(instance.retryAt!.getTime()).toBeLessThanOrEqual( + maxRunAt + timeoutMinutes * 60 * 1000 ); expect(instance.enabled).not.toBeDefined(); }); @@ -742,8 +755,12 @@ describe('TaskManagerRunner', () => { const instance = store.update.mock.calls[0][0]; expect(instance.id).toEqual(id); - const expectedRunAt = new Date(Date.now() + calculateDelay(initialAttempts)); - expect(instance.runAt.getTime()).toEqual(expectedRunAt.getTime()); + + const minRunAt = Date.now(); + const maxRunAt = minRunAt + baseDelay * Math.pow(2, initialAttempts - 2); + expect(instance.runAt.getTime()).toBeGreaterThanOrEqual(minRunAt); + expect(instance.runAt.getTime()).toBeLessThanOrEqual(maxRunAt); + expect(instance.params).toEqual({ a: 'b' }); expect(instance.state).toEqual({ hey: 'there' }); expect(instance.enabled).not.toBeDefined(); @@ -1098,8 +1115,11 @@ describe('TaskManagerRunner', () => { expect(store.update).toHaveBeenCalledTimes(1); const instance = store.update.mock.calls[0][0]; - const expectedRunAt = new Date(Date.now() + calculateDelay(initialAttempts)); - expect(instance.runAt.getTime()).toEqual(expectedRunAt.getTime()); + const minRunAt = Date.now(); + const maxRunAt = minRunAt + baseDelay * Math.pow(2, initialAttempts - 2); + expect(instance.runAt.getTime()).toBeGreaterThanOrEqual(minRunAt); + expect(instance.runAt.getTime()).toBeLessThanOrEqual(maxRunAt); + expect(instance.enabled).not.toBeDefined(); }); @@ -2542,6 +2562,26 @@ describe('TaskManagerRunner', () => { `Error encountered when running onTaskRemoved() hook for testbar2 "foo": Fail` ); }); + + describe('calculateDelay', () => { + it('returns 30s on the first attempt', () => { + expect(calculateDelay(1)).toBe(30000); + }); + + it('returns delay with jitter', () => { + const delay = calculateDelay(5); + // with jitter should be random between 0 and 40 min (inclusive) + expect(delay).toBeGreaterThanOrEqual(0); + expect(delay).toBeLessThanOrEqual(2400000); + }); + + it('returns delay capped at 1 hour', () => { + const delay = calculateDelay(10); + // with jitter should be random between 0 and 1 hr (inclusive) + expect(delay).toBeGreaterThanOrEqual(0); + expect(delay).toBeLessThanOrEqual(60 * 60 * 1000); + }); + }); }); interface TestOpts { diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index f67d8a22db81d5..51b0a182d10a20 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -14,7 +14,7 @@ import apm from 'elastic-apm-node'; import { v4 as uuidv4 } from 'uuid'; import { withSpan } from '@kbn/apm-utils'; -import { defaults, flow, identity, isUndefined, omit } from 'lodash'; +import { defaults, flow, identity, isUndefined, omit, random } from 'lodash'; import { ExecutionContextStart, Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import moment from 'moment'; @@ -931,12 +931,16 @@ export function asRan(task: InstanceOf): RanTask } export function calculateDelay(attempts: number) { + // Return 30s for the first retry attempt if (attempts === 1) { - return 30 * 1000; // 30s + return 30 * 1000; } else { - // get multiples of 5 min const defaultBackoffPerFailure = 5 * 60 * 1000; - return defaultBackoffPerFailure * Math.pow(2, attempts - 2); + const maxDelay = 60 * 60 * 1000; + // For each remaining attempt return an exponential delay with jitter that is capped at 1 hour. + // We adjust the attempts by 2 to ensure that delay starts at 5m for the second retry attempt + // and increases exponentially from there. + return random(Math.min(maxDelay, defaultBackoffPerFailure * Math.pow(2, attempts - 2))); } }