From 702b207d2d93019e9efa3f00fdd48c9328c2f893 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Fri, 5 Jan 2024 11:30:10 +0100 Subject: [PATCH 01/10] [Custom threshold] Add log rate analysis to the alert details page for one document count aggregation (#174031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #171163 ## Summary This PR adds log rate analysis to the alert details page for one document count aggregation if the user has a license above platinum. This is a similar implementation in the log threshold alert details page. ![image](https://github.com/elastic/kibana/assets/12370520/29cd29bf-ead5-4574-8121-739d3ed11fe7) ## 🧪 How to test? - Create a Custom threshold rule with only one document count aggregation - Optional filter, document count aggregation filter, and group by information will be applied to the query of this component. - Go to the alert details page, you should see the log rate analysis on this page --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/custom_threshold_rule/types.ts | 5 + .../utils/get_interval_in_seconds.test.ts | 28 +++ .../common/utils/get_interval_in_seconds.ts | 32 +++ x-pack/plugins/observability/kibana.jsonc | 1 + .../alert_search_bar/alert_search_bar.tsx | 22 +- .../alert_details_app_section.test.tsx.snap | 0 .../alert_details_app_section.test.tsx | 8 +- .../alert_details_app_section.tsx | 28 ++- .../log_rate_analysis_query.test.ts.snap | 222 ++++++++++++++++ .../helpers/get_initial_analysis_start.ts | 58 +++++ .../helpers/log_rate_analysis_query.test.ts | 98 ++++++++ .../helpers/log_rate_analysis_query.ts | 62 +++++ .../log_rate_analysis.tsx | 238 ++++++++++++++++++ .../mocks/custom_threshold_rule.ts | 5 +- .../observability/public/hooks/use_license.ts | 4 +- .../public/pages/overview/overview.tsx | 18 +- x-pack/plugins/observability/public/plugin.ts | 3 + .../register_observability_rule_types.ts | 5 +- .../build_es_query/build_es_query.test.ts | 2 +- .../utils/build_es_query/build_es_query.ts | 14 +- .../custom_threshold/lib/evaluate_rule.ts | 3 +- .../lib/rules/custom_threshold/utils.ts | 26 -- x-pack/plugins/observability/tsconfig.json | 4 +- 23 files changed, 816 insertions(+), 70 deletions(-) create mode 100644 x-pack/plugins/observability/common/utils/get_interval_in_seconds.test.ts create mode 100644 x-pack/plugins/observability/common/utils/get_interval_in_seconds.ts rename x-pack/plugins/observability/public/components/custom_threshold/components/{ => alert_details_app_section}/__snapshots__/alert_details_app_section.test.tsx.snap (100%) rename x-pack/plugins/observability/public/components/custom_threshold/components/{ => alert_details_app_section}/alert_details_app_section.test.tsx (94%) rename x-pack/plugins/observability/public/components/custom_threshold/components/{ => alert_details_app_section}/alert_details_app_section.tsx (86%) create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/__snapshots__/log_rate_analysis_query.test.ts.snap create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_initial_analysis_start.ts create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.test.ts create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts create mode 100644 x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index 7668e19dc3900..67849df1b59d7 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -107,6 +107,11 @@ export enum InfraFormatterType { percent = 'percent', } +// Custom threshold alert types + +// Alert fields['kibana.alert.group] type +export type GroupBy = Array<{ field: string; value: string }>; + /* * Utils * diff --git a/x-pack/plugins/observability/common/utils/get_interval_in_seconds.test.ts b/x-pack/plugins/observability/common/utils/get_interval_in_seconds.test.ts new file mode 100644 index 0000000000000..f2654a224ae86 --- /dev/null +++ b/x-pack/plugins/observability/common/utils/get_interval_in_seconds.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getIntervalInSeconds } from './get_interval_in_seconds'; + +describe('getIntervalInSeconds', () => { + const testData = [ + { interval: '5ms', result: 0.005 }, + { interval: '70s', result: 70 }, + { interval: '25m', result: 1500 }, + { interval: '10h', result: 36000 }, + { interval: '3d', result: 259200 }, + { interval: '1w', result: 604800 }, + { interval: '1y', result: 30758400 }, + ]; + + it.each(testData)('getIntervalInSeconds($interval) = $result', ({ interval, result }) => { + expect(getIntervalInSeconds(interval)).toBe(result); + }); + + it('Throws error if interval is not valid', () => { + expect(() => getIntervalInSeconds('invalid')).toThrow('Invalid interval string format.'); + }); +}); diff --git a/x-pack/plugins/observability/common/utils/get_interval_in_seconds.ts b/x-pack/plugins/observability/common/utils/get_interval_in_seconds.ts new file mode 100644 index 0000000000000..6ebdbb83bdc62 --- /dev/null +++ b/x-pack/plugins/observability/common/utils/get_interval_in_seconds.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const intervalUnits = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']; +const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + intervalUnits.join('|') + ')$'); + +interface UnitsToSeconds { + [unit: string]: number; +} + +const units: UnitsToSeconds = { + ms: 0.001, + s: 1, + m: 60, + h: 3600, + d: 86400, + w: 86400 * 7, + M: 86400 * 30, + y: 86400 * 356, +}; + +export const getIntervalInSeconds = (interval: string): number => { + const matches = interval.match(INTERVAL_STRING_RE); + if (matches) { + return parseFloat(matches[1]) * units[matches[2]]; + } + throw new Error('Invalid interval string format.'); +}; diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index 526c283c0f0be..c7fdb22b5f792 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -20,6 +20,7 @@ "dataViews", "dataViewEditor", "embeddable", + "fieldFormats", "uiActions", "presentationUtil", "exploratoryView", diff --git a/x-pack/plugins/observability/public/components/alert_search_bar/alert_search_bar.tsx b/x-pack/plugins/observability/public/components/alert_search_bar/alert_search_bar.tsx index d27e32970f2fa..ff69c8e2ff2c3 100644 --- a/x-pack/plugins/observability/public/components/alert_search_bar/alert_search_bar.tsx +++ b/x-pack/plugins/observability/public/components/alert_search_bar/alert_search_bar.tsx @@ -47,15 +47,15 @@ export function ObservabilityAlertSearchBar({ (alertStatus: AlertStatus) => { try { onEsQueryChange( - buildEsQuery( - { + buildEsQuery({ + timeRange: { to: rangeTo, from: rangeFrom, }, kuery, - [...getAlertStatusQuery(alertStatus), ...defaultSearchQueries], - getEsQueryConfig(uiSettings) - ) + queries: [...getAlertStatusQuery(alertStatus), ...defaultSearchQueries], + config: getEsQueryConfig(uiSettings), + }) ); } catch (error) { toasts.addError(error, { @@ -89,15 +89,15 @@ export function ObservabilityAlertSearchBar({ ({ dateRange, query }) => { try { // First try to create es query to make sure query is valid, then save it in state - const esQuery = buildEsQuery( - { + const esQuery = buildEsQuery({ + timeRange: { to: dateRange.to, from: dateRange.from, }, - query, - [...getAlertStatusQuery(status), ...defaultSearchQueries], - getEsQueryConfig(uiSettings) - ); + kuery: query, + queries: [...getAlertStatusQuery(status), ...defaultSearchQueries], + config: getEsQueryConfig(uiSettings), + }); if (query) onKueryChange(query); timeFilterService.setTime(dateRange); onRangeFromChange(dateRange.from); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap similarity index 100% rename from x-pack/plugins/observability/public/components/custom_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx similarity index 94% rename from x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx index 8747558251da3..c623d9aa15043 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx @@ -15,9 +15,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { buildCustomThresholdAlert, buildCustomThresholdRule, -} from '../mocks/custom_threshold_rule'; +} from '../../mocks/custom_threshold_rule'; import AlertDetailsAppSection from './alert_details_app_section'; -import { ExpressionChart } from './expression_chart'; +import { ExpressionChart } from '../expression_chart'; const mockedChartStartContract = chartPluginMock.createStartContract(); @@ -33,11 +33,11 @@ jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ }), })); -jest.mock('./expression_chart', () => ({ +jest.mock('../expression_chart', () => ({ ExpressionChart: jest.fn(() =>
), })); -jest.mock('../../../utils/kibana_react', () => ({ +jest.mock('../../../../utils/kibana_react', () => ({ useKibana: () => ({ services: { ...mockCoreMock.createStart(), diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx similarity index 86% rename from x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx rename to x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index ce9a651a7a1e3..3c95f0d6677b6 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -25,15 +25,16 @@ import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common'; import { AlertAnnotation, AlertActiveTimeRangeAnnotation } from '@kbn/observability-alert-details'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import { DataView } from '@kbn/data-views-plugin/common'; -import { MetricsExplorerChartType } from '../../../../common/custom_threshold_rule/types'; -import { useKibana } from '../../../utils/kibana_react'; -import { metricValueFormatter } from '../../../../common/custom_threshold_rule/metric_value_formatter'; -import { AlertSummaryField, TopAlert } from '../../..'; - -import { ExpressionChart } from './expression_chart'; -import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart'; -import { Threshold } from './custom_threshold'; -import { AlertParams, CustomThresholdRuleTypeParams } from '../types'; +import { MetricsExplorerChartType } from '../../../../../common/custom_threshold_rule/types'; +import { useLicense } from '../../../../hooks/use_license'; +import { useKibana } from '../../../../utils/kibana_react'; +import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter'; +import { AlertSummaryField, TopAlert } from '../../../..'; +import { AlertParams, CustomThresholdRuleTypeParams } from '../../types'; +import { ExpressionChart } from '../expression_chart'; +import { TIME_LABELS } from '../criterion_preview_chart/criterion_preview_chart'; +import { Threshold } from '../custom_threshold'; +import { LogRateAnalysis } from './log_rate_analysis'; // TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 export type CustomThresholdRule = Rule; @@ -57,8 +58,11 @@ export default function AlertDetailsAppSection({ ruleLink, setAlertSummaryFields, }: AppSectionProps) { - const { uiSettings, charts, data } = useKibana().services; + const services = useKibana().services; + const { uiSettings, charts, data } = services; const { euiTheme } = useEuiTheme(); + const { hasAtLeast } = useLicense(); + const hasLogRateAnalysisLicense = hasAtLeast('platinum'); const [dataView, setDataView] = useState(); const [, setDataViewError] = useState(); const ruleParams = rule.params as RuleTypeParams & AlertParams; @@ -83,6 +87,7 @@ export default function AlertDetailsAppSection({ key={ALERT_TIME_RANGE_ANNOTATION_ID} />, ]; + useEffect(() => { setAlertSummaryFields([ { @@ -181,6 +186,9 @@ export default function AlertDetailsAppSection({ ))} + {hasLogRateAnalysisLicense && ( + + )} ) : null; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/__snapshots__/log_rate_analysis_query.test.ts.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/__snapshots__/log_rate_analysis_query.test.ts.snap new file mode 100644 index 0000000000000..9a9eb41224222 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/__snapshots__/log_rate_analysis_query.test.ts.snap @@ -0,0 +1,222 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`buildEsQuery should generate correct es query for rule with multiple metrics 1`] = `undefined`; + +exports[`buildEsQuery should generate correct es query for rule with optional filer, count filter and WITHOUT group by 1`] = ` +Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "optionalFilter": "container-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "host.name": "host-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "host.name": "host-2", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, +} +`; + +exports[`buildEsQuery should generate correct es query for rule with optional filer, count filter and group by 1`] = ` +Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "optionalFilter": "container-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "host.name": "host-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "host.name": "host-2", + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "groupByField": "groupByValue", + }, + }, + ], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, +} +`; + +exports[`buildEsQuery should generate correct es query for rule with optional filer, count filter and multiple group by 1`] = ` +Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "optionalFilter": "container-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "host.name": "host-1", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "host.name": "host-2", + }, + }, + ], + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "groupByField": "groupByValue", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "secondGroupByField": "secondGroupByValue", + }, + }, + ], + }, + }, + ], + }, + }, + ], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, +} +`; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_initial_analysis_start.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_initial_analysis_start.ts new file mode 100644 index 0000000000000..08bc835b7f43e --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/get_initial_analysis_start.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment, { Moment } from 'moment'; + +export interface GetInitialAnalysisStartArgs { + alertStart: Moment; + intervalFactor: number; + alertEnd?: Moment; +} + +export const getDeviationMax = ({ + alertStart, + intervalFactor, + alertEnd, +}: GetInitialAnalysisStartArgs) => { + if (alertEnd) { + return alertEnd + .clone() + .subtract(1 * intervalFactor, 'minutes') + .valueOf(); + } else if ( + alertStart + .clone() + .add(10 * intervalFactor, 'minutes') + .isAfter(moment(new Date())) + ) { + return moment(new Date()).valueOf(); + } else { + return alertStart + .clone() + .add(10 * intervalFactor, 'minutes') + .valueOf(); + } +}; + +export const getInitialAnalysisStart = (args: GetInitialAnalysisStartArgs) => { + const { alertStart, intervalFactor } = args; + return { + baselineMin: alertStart + .clone() + .subtract(13 * intervalFactor, 'minutes') + .valueOf(), + baselineMax: alertStart + .clone() + .subtract(2 * intervalFactor, 'minutes') + .valueOf(), + deviationMin: alertStart + .clone() + .subtract(1 * intervalFactor, 'minutes') + .valueOf(), + deviationMax: getDeviationMax(args), + }; +}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.test.ts new file mode 100644 index 0000000000000..8169816800e4d --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.test.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Aggregators, Comparator } from '../../../../../../common/custom_threshold_rule/types'; +import { CustomThresholdRuleTypeParams } from '../../../types'; +import { getLogRateAnalysisEQQuery } from './log_rate_analysis_query'; + +describe('buildEsQuery', () => { + const index = 'changedMockedIndex'; + const mockedParams: CustomThresholdRuleTypeParams = { + groupBy: ['host.hostname'], + searchConfiguration: { + index, + query: { query: 'optionalFilter: container-1', language: 'kuery' }, + }, + criteria: [ + { + metrics: [ + { + name: 'A', + aggType: Aggregators.COUNT, + filter: 'host.name: host-1 or host.name: host-2', + }, + ], + timeSize: 1, + timeUnit: 'm', + threshold: [90], + comparator: Comparator.GT, + }, + ], + }; + const mockedAlertWithMultipleGroups = { + fields: { + 'kibana.alert.group': [ + { + field: 'groupByField', + value: 'groupByValue', + }, + { + field: 'secondGroupByField', + value: 'secondGroupByValue', + }, + ], + }, + }; + const testData: Array<{ + title: string; + params: CustomThresholdRuleTypeParams; + alert: any; + }> = [ + { + title: 'rule with optional filer, count filter and group by', + params: mockedParams, + alert: { + fields: { + 'kibana.alert.group': [mockedAlertWithMultipleGroups.fields['kibana.alert.group'][0]], + }, + }, + }, + { + title: 'rule with optional filer, count filter and multiple group by', + params: mockedParams, + alert: mockedAlertWithMultipleGroups, + }, + { + title: 'rule with optional filer, count filter and WITHOUT group by', + params: mockedParams, + alert: {}, + }, + { + title: 'rule with multiple metrics', + params: { + ...mockedParams, + criteria: [ + { + metrics: [ + { name: 'A', aggType: Aggregators.COUNT, filter: 'host.name: host-1' }, + { name: 'B', aggType: Aggregators.AVERAGE, field: 'system.load.1' }, + ], + timeSize: 1, + timeUnit: 'm', + threshold: [90], + comparator: Comparator.GT, + }, + ], + }, + alert: {}, + }, + ]; + + test.each(testData)('should generate correct es query for $title', ({ alert, params }) => { + expect(getLogRateAnalysisEQQuery(alert, params)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts new file mode 100644 index 0000000000000..b9eef4643802a --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { Aggregators, GroupBy } from '../../../../../../common/custom_threshold_rule/types'; +import { buildEsQuery } from '../../../../../utils/build_es_query'; +import type { TopAlert } from '../../../../../typings/alerts'; +import type { CustomThresholdRuleTypeParams } from '../../../types'; +import type { CustomThresholdExpressionMetric } from '../../../../../../common/custom_threshold_rule/types'; + +const getKuery = ( + metrics: CustomThresholdExpressionMetric[], + filter?: string, + groupBy?: GroupBy +) => { + let query = ''; + const isOneCountConditionWithFilter = + metrics.length === 1 && metrics[0].aggType === 'count' && metrics[0].filter; + + if (filter && isOneCountConditionWithFilter) { + query = `(${filter}) and (${metrics[0].filter})`; + } else if (isOneCountConditionWithFilter) { + query = `(${metrics[0].filter!})`; + } else if (filter) { + query = `(${filter})`; + } + + if (groupBy) { + groupBy.forEach(({ field, value }) => { + query += ` and ${field}: ${value}`; + }); + } + + return query; +}; + +export const getLogRateAnalysisEQQuery = ( + alert: TopAlert>, + params: CustomThresholdRuleTypeParams +): QueryDslQueryContainer | undefined => { + // We only show log rate analysis for one condition with one count aggregation + if ( + params.criteria.length !== 1 || + params.criteria[0].metrics.length !== 1 || + params.criteria[0].metrics[0].aggType !== Aggregators.COUNT + ) { + return; + } + + const groupBy: GroupBy | undefined = get(alert, 'fields["kibana.alert.group"]'); + const optionalFilter: string | undefined = get(params.searchConfiguration, 'query.query'); + const boolQuery = buildEsQuery({ + kuery: getKuery(params.criteria[0].metrics, optionalFilter, groupBy), + }); + + return boolQuery; +}; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx new file mode 100644 index 0000000000000..7578f0979907f --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx @@ -0,0 +1,238 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick, orderBy } from 'lodash'; +import moment from 'moment'; +import React, { useEffect, useMemo, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; +import { + LOG_RATE_ANALYSIS_TYPE, + type LogRateAnalysisType, +} from '@kbn/aiops-utils/log_rate_analysis_type'; +import { LogRateAnalysisContent, type LogRateAnalysisResultsData } from '@kbn/aiops-plugin/public'; +import { Rule } from '@kbn/alerting-plugin/common'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { type Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; +import { ALERT_END } from '@kbn/rule-data-utils'; +import { CustomThresholdRuleTypeParams } from '../../types'; +import { TopAlert } from '../../../..'; +import { Color, colorTransformer } from '../../../../../common/custom_threshold_rule/color_palette'; +import { getLogRateAnalysisEQQuery } from './helpers/log_rate_analysis_query'; +import { getInitialAnalysisStart } from './helpers/get_initial_analysis_start'; + +export interface AlertDetailsLogRateAnalysisProps { + alert: TopAlert>; + dataView: any; + rule: Rule; + services: any; +} + +interface SignificantFieldValue { + field: string; + value: string | number; + docCount: number; + pValue: number | null; +} + +export function LogRateAnalysis({ + alert, + dataView, + rule, + services, +}: AlertDetailsLogRateAnalysisProps) { + const { + observabilityAIAssistant: { ObservabilityAIAssistantContextualInsight }, + } = services; + const [esSearchQuery, setEsSearchQuery] = useState(); + const [logRateAnalysisParams, setLogRateAnalysisParams] = useState< + | { logRateAnalysisType: LogRateAnalysisType; significantFieldValues: SignificantFieldValue[] } + | undefined + >(); + + useEffect(() => { + const esSearchRequest = getLogRateAnalysisEQQuery(alert, rule.params); + + if (esSearchRequest) { + setEsSearchQuery(esSearchRequest); + } + }, [alert, rule.params]); + + // Identify `intervalFactor` to adjust time ranges based on alert settings. + // The default time ranges for `initialAnalysisStart` are suitable for a `1m` lookback. + // If an alert would have a `5m` lookback, this would result in a factor of `5`. + const lookbackDuration = + alert.fields['kibana.alert.rule.parameters'] && + alert.fields['kibana.alert.rule.parameters'].timeSize && + alert.fields['kibana.alert.rule.parameters'].timeUnit + ? moment.duration( + alert.fields['kibana.alert.rule.parameters'].timeSize as number, + alert.fields['kibana.alert.rule.parameters'].timeUnit as any + ) + : moment.duration(1, 'm'); + const intervalFactor = Math.max(1, lookbackDuration.asSeconds() / 60); + + const alertStart = moment(alert.start); + const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]) : undefined; + + const timeRange = { + min: alertStart.clone().subtract(15 * intervalFactor, 'minutes'), + max: alertEnd ? alertEnd.clone().add(1 * intervalFactor, 'minutes') : moment(new Date()), + }; + + const logRateAnalysisTitle = i18n.translate( + 'xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle', + { + defaultMessage: 'Possible causes and remediations', + } + ); + + const onAnalysisCompleted = (analysisResults: LogRateAnalysisResultsData | undefined) => { + const significantFieldValues = orderBy( + analysisResults?.significantItems?.map((item) => ({ + field: item.fieldName, + value: item.fieldValue, + docCount: item.doc_count, + pValue: item.pValue, + })), + ['pValue', 'docCount'], + ['asc', 'asc'] + ).slice(0, 50); + + const logRateAnalysisType = analysisResults?.analysisType; + setLogRateAnalysisParams( + significantFieldValues && logRateAnalysisType + ? { logRateAnalysisType, significantFieldValues } + : undefined + ); + }; + + const messages = useMemo(() => { + const hasLogRateAnalysisParams = + logRateAnalysisParams && logRateAnalysisParams.significantFieldValues?.length > 0; + + if (!hasLogRateAnalysisParams) { + return undefined; + } + + const { logRateAnalysisType } = logRateAnalysisParams; + + const header = 'Field name,Field value,Doc count,p-value'; + const rows = logRateAnalysisParams.significantFieldValues + .map((item) => Object.values(item).join(',')) + .join('\n'); + + const content = `You are an observability expert using Elastic Observability Suite on call being consulted about a log threshold alert that got triggered by a ${logRateAnalysisType} in log messages. Your job is to take immediate action and proceed with both urgency and precision. + "Log Rate Analysis" is an AIOps feature that uses advanced statistical methods to identify reasons for increases and decreases in log rates. It makes it easy to find and investigate causes of unusual spikes or dips by using the analysis workflow view. + You are using "Log Rate Analysis" and ran the statistical analysis on the log messages which occured during the alert. + You received the following analysis results from "Log Rate Analysis" which list statistically significant co-occuring field/value combinations sorted from most significant (lower p-values) to least significant (higher p-values) that ${ + logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE + ? 'contribute to the log rate spike' + : 'are less or not present in the log rate dip' + }: + + ${ + logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE + ? 'The median log rate in the selected deviation time range is higher than the baseline. Therefore, the results shows statistically significant items within the deviation time range that are contributors to the spike. The "doc count" column refers to the amount of documents in the deviation time range.' + : 'The median log rate in the selected deviation time range is lower than the baseline. Therefore, the analysis results table shows statistically significant items within the baseline time range that are less in number or missing within the deviation time range. The "doc count" column refers to the amount of documents in the baseline time range.' + } + + ${header} + ${rows} + + Based on the above analysis results and your observability expert knowledge, output the following: + Analyse the type of these logs and explain their usual purpose (1 paragraph). + ${ + logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE + ? 'Based on the type of these logs do a root cause analysis on why the field and value combinations from the analysis results are causing this log rate spike (2 parapraphs)' + : 'Based on the type of these logs explain why the statistically significant field and value combinations are less in number or missing from the log rate dip with concrete examples based on the analysis results data which contains items that are present in the baseline time range and are missing or less in number in the deviation time range (2 paragraphs)' + }. + ${ + logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE + ? 'Recommend concrete remediations to resolve the root cause (3 bullet points).' + : '' + } + + Do not mention individual p-values from the analysis results. + Do not repeat the full list of field names and field values back to the user. + Do not guess, just say what you are sure of. Do not repeat the given instructions in your output.`; + + const now = new Date().toISOString(); + + return [ + { + '@timestamp': now, + message: { + content, + role: MessageRole.User, + }, + }, + ]; + }, [logRateAnalysisParams]); + + if (!dataView || !esSearchQuery) return null; + + return ( + + + + +

+ +

+
+
+ + + +
+ + {ObservabilityAIAssistantContextualInsight && messages ? ( + + + + ) : null} + +
+ ); +} diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index cfe41b6bd9d04..817e5dbadb978 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -8,7 +8,10 @@ import { v4 as uuidv4 } from 'uuid'; import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; -import { CustomThresholdAlert, CustomThresholdRule } from '../components/alert_details_app_section'; +import { + CustomThresholdAlert, + CustomThresholdRule, +} from '../components/alert_details_app_section/alert_details_app_section'; export const buildCustomThresholdRule = ( rule: Partial = {} diff --git a/x-pack/plugins/observability/public/hooks/use_license.ts b/x-pack/plugins/observability/public/hooks/use_license.ts index f666dcd025bd3..f6ed1ebf8bab1 100644 --- a/x-pack/plugins/observability/public/hooks/use_license.ts +++ b/x-pack/plugins/observability/public/hooks/use_license.ts @@ -14,7 +14,7 @@ import { useKibana } from '../utils/kibana_react'; interface UseLicenseReturnValue { getLicense: () => ILicense | null; - hasAtLeast: (level: LicenseType) => boolean | undefined; + hasAtLeast: (level: LicenseType) => boolean; } export const useLicense = (): UseLicenseReturnValue => { @@ -25,7 +25,7 @@ export const useLicense = (): UseLicenseReturnValue => { getLicense: () => license, hasAtLeast: useCallback( (level: LicenseType) => { - if (!license) return; + if (!license) return false; return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level); }, diff --git a/x-pack/plugins/observability/public/pages/overview/overview.tsx b/x-pack/plugins/observability/public/pages/overview/overview.tsx index 17121be51c668..5cc5e2ac967ea 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.tsx @@ -73,8 +73,10 @@ export function OverviewPage() { const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>( buildEsQuery({ - from: relativeStart, - to: relativeEnd, + timeRange: { + from: relativeStart, + to: relativeEnd, + }, }) ); @@ -108,8 +110,10 @@ export function OverviewPage() { useEffect(() => { setEsQuery( buildEsQuery({ - from: relativeStart, - to: relativeEnd, + timeRange: { + from: relativeStart, + to: relativeEnd, + }, }) ); }, [relativeEnd, relativeStart]); @@ -117,8 +121,10 @@ export function OverviewPage() { const handleTimeRangeRefresh = useCallback(() => { setEsQuery( buildEsQuery({ - from: relativeStart, - to: relativeEnd, + timeRange: { + from: relativeStart, + to: relativeEnd, + }, }) ); }, [relativeEnd, relativeStart]); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 808573579cb99..03322f7817282 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -28,6 +28,7 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { LOG_EXPLORER_LOCATOR_ID, LogExplorerLocatorParams } from '@kbn/deeplinks-observability'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import type { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; import { i18n } from '@kbn/i18n'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; @@ -114,6 +115,7 @@ export interface ConfigSchema { export type ObservabilityPublicSetup = ReturnType; export interface ObservabilityPublicPluginsSetup { data: DataPublicPluginSetup; + fieldFormats: FieldFormatsSetup; observabilityShared: ObservabilitySharedPluginSetup; observabilityAIAssistant: ObservabilityAIAssistantPluginSetup; share: SharePluginSetup; @@ -137,6 +139,7 @@ export interface ObservabilityPublicPluginsStart { discover: DiscoverStart; embeddable: EmbeddableStart; exploratoryView: ExploratoryViewPublicStart; + fieldFormats: FieldFormatsStart; guidedOnboarding?: GuidedOnboardingPluginStart; lens: LensPublicStart; licensing: LicensingPluginStart; diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index c9079991bdf4d..04519eba23d5f 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -156,7 +156,10 @@ export const registerObservabilityRuleTypes = async ( }; }, alertDetailsAppSection: lazy( - () => import('../components/custom_threshold/components/alert_details_app_section') + () => + import( + '../components/custom_threshold/components/alert_details_app_section/alert_details_app_section' + ) ), priority: 5, }); diff --git a/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.test.ts b/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.test.ts index 4bbacaa7bb1ad..3cb37893e6169 100644 --- a/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.test.ts +++ b/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.test.ts @@ -37,6 +37,6 @@ describe('buildEsQuery', () => { ]; test.each(testData)('should generate correct es query for %j', ({ kuery, timeRange }) => { - expect(buildEsQuery(timeRange, kuery)).toMatchSnapshot(); + expect(buildEsQuery({ timeRange, kuery })).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.ts b/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.ts index 1fd26690f86d5..acf6b4cfc4a7e 100644 --- a/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.ts +++ b/x-pack/plugins/observability/public/utils/build_es_query/build_es_query.ts @@ -9,12 +9,14 @@ import { buildEsQuery as kbnBuildEsQuery, TimeRange, Query, EsQueryConfig } from import { ALERT_TIME_RANGE } from '@kbn/rule-data-utils'; import { getTime } from '@kbn/data-plugin/common'; -export function buildEsQuery( - timeRange: TimeRange, - kuery?: string, - queries: Query[] = [], - config: EsQueryConfig = {} -) { +interface BuildEsQueryArgs { + timeRange?: TimeRange; + kuery?: string; + queries?: Query[]; + config?: EsQueryConfig; +} + +export function buildEsQuery({ timeRange, kuery, queries = [], config = {} }: BuildEsQueryArgs) { const timeFilter = timeRange && getTime(undefined, timeRange, { diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index b1f790da9d146..f5b3f0adf1bdd 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -9,7 +9,8 @@ import moment from 'moment'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; -import { AdditionalContext, getIntervalInSeconds } from '../utils'; +import { getIntervalInSeconds } from '../../../../../common/utils/get_interval_in_seconds'; +import { AdditionalContext } from '../utils'; import { SearchConfigurationType } from '../types'; import { createTimerange } from './create_timerange'; import { getData } from './get_data'; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts index 66049ab431c5d..59d7cf93e0ea4 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/utils.ts @@ -257,32 +257,6 @@ export const isTooManyBucketsPreviewException = ( ): value is TooManyBucketsPreviewExceptionMetadata => Boolean(value && value.TOO_MANY_BUCKETS_PREVIEW_EXCEPTION); -const intervalUnits = ['y', 'M', 'w', 'd', 'h', 'm', 's', 'ms']; -const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + intervalUnits.join('|') + ')$'); - -interface UnitsToSeconds { - [unit: string]: number; -} - -const units: UnitsToSeconds = { - ms: 0.001, - s: 1, - m: 60, - h: 3600, - d: 86400, - w: 86400 * 7, - M: 86400 * 30, - y: 86400 * 356, -}; - -export const getIntervalInSeconds = (interval: string): number => { - const matches = interval.match(INTERVAL_STRING_RE); - if (matches) { - return parseFloat(matches[1]) * units[matches[2]]; - } - throw new Error('Invalid interval string format.'); -}; - export const calculateRateTimeranges = (timerange: { to: number; from: number }) => { // This is the total number of milliseconds for the entire timerange const totalTime = timerange.to - timerange.from; diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index ba9ac59725adc..04f853572085f 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -102,7 +102,9 @@ "@kbn/core-elasticsearch-client-server-mocks", "@kbn/ingest-pipelines-plugin", "@kbn/core-saved-objects-api-server-mocks", - "@kbn/core-ui-settings-browser-mocks" + "@kbn/core-ui-settings-browser-mocks", + "@kbn/field-formats-plugin", + "@kbn/aiops-utils" ], "exclude": [ "target/**/*" From 42272f90c05616e1ea6bc4f80bc140f3a4979910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Fri, 5 Jan 2024 11:08:38 +0000 Subject: [PATCH 02/10] [Serverless Sidenav] Order active paths by tree depth (#174184) --- .../src/project_navigation/utils.test.ts | 55 ++++++++++--------- .../src/project_navigation/utils.ts | 3 + .../components/side_navigation/index.tsx | 2 - 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.test.ts index a207162e060cb..d886ee3a5d6ac 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.test.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.test.ts @@ -133,33 +133,36 @@ describe('findActiveNodes', () => { title: 'Root', path: 'root', }, + // Group 1 '[0][0]': { id: 'group1', title: 'Group 1', - deepLink: getDeepLink('group1', 'group1'), path: 'root.group1', }, '[0][0][0]': { - id: 'group1A', - title: 'Group 1A', - path: 'root.group1.group1A', - }, - '[0][0][0][0]': { id: 'item1', title: 'Item 1', - deepLink: getDeepLink('item1', 'item1'), - path: 'root.group1.group1A.item1', + deepLink: getDeepLink('item1', 'item1'), // First match + path: 'root.group1.item1', }, + // Group 2 '[0][1]': { id: 'group2', title: 'Group 2', + deepLink: getDeepLink('group2', 'group2'), path: 'root.group2', }, '[0][1][0]': { + id: 'group2A', + title: 'Group 2A', + path: 'root.group2.group2A', + }, + '[0][1][0][0]': { id: 'item2', title: 'Item 2', - deepLink: getDeepLink('item1', 'item1'), // Same link as above, should match both - path: 'root.group2.item2', + // Second match --> should come first as it is the longest match of the 2 + deepLink: getDeepLink('item1', 'item1'), + path: 'root.group2.group2A.item2', }, }; @@ -172,21 +175,21 @@ describe('findActiveNodes', () => { path: 'root', }, { - id: 'group1', - title: 'Group 1', - deepLink: getDeepLink('group1', 'group1'), - path: 'root.group1', + id: 'group2', + title: 'Group 2', + deepLink: getDeepLink('group2', 'group2'), + path: 'root.group2', }, { - id: 'group1A', - title: 'Group 1A', - path: 'root.group1.group1A', + id: 'group2A', + title: 'Group 2A', + path: 'root.group2.group2A', }, { - id: 'item1', - title: 'Item 1', + id: 'item2', + title: 'Item 2', deepLink: getDeepLink('item1', 'item1'), - path: 'root.group1.group1A.item1', + path: 'root.group2.group2A.item2', }, ], [ @@ -196,15 +199,15 @@ describe('findActiveNodes', () => { path: 'root', }, { - id: 'group2', - title: 'Group 2', - path: 'root.group2', + id: 'group1', + title: 'Group 1', + path: 'root.group1', }, { - id: 'item2', - title: 'Item 2', + id: 'item1', + title: 'Item 1', deepLink: getDeepLink('item1', 'item1'), - path: 'root.group2.item2', + path: 'root.group1.item1', }, ], ]); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.ts index 63f7f8e612c2e..c025872d736b0 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/utils.ts @@ -137,6 +137,9 @@ export const findActiveNodes = ( matches[length] = []; } matches[length].push(key); + // If there are multiple node matches of the same URL path length, we want to order them by + // tree depth, so that the longest match (deepest node) comes first. + matches[length].sort((a, b) => b.length - a.length); } } }); diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx index 5be456cfd5f3c..87e6dc3c7b567 100644 --- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx @@ -33,8 +33,6 @@ const navigationTree: NavigationTreeDefinition = { defaultMessage: 'Discover', }), link: 'observability-log-explorer', - // prevent this entry from ever becoming active, effectively falling through to the obs-log-explorer child - getIsActive: () => false, // avoid duplicate "Discover" breadcrumbs breadcrumbStatus: 'hidden', renderAs: 'item', From 403a8d0604c1a3310470c7219a05c53017f27816 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Fri, 5 Jan 2024 12:30:12 +0100 Subject: [PATCH 03/10] [APM] Fix max bucket error in Dependencies pages (#173083) fixes: https://github.com/elastic/kibana/issues/161239 ## Summary This PR changes how dependencies data is fetched. To mitigate the max bucket error risk, and, at the same time, keep the compatibility with the dependencies-related pages that consume the `get_connection_stats` query, it now paginates the composite aggregation, using smaller batches of 1k items per pagination. **10k dependencies** image **500 dependencies per service** image **Notes:** Fetching 1k on each iteration might make the page slower; The max bucket error might still happen depending on the date range or how services are instrumented. ### How to test 1. Run synthtrace ``` node scripts/synthtrace service_many_dependencies.ts --from=now-15m --to=now --clean ``` 2. Navigate to the Dependencies Inventory page 3. Navigate to a service overview and then to the dependency tab --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../scenarios/service_many_dependencies.ts | 67 +++++++++ .../e2e/{ => dependencies}/dependencies.cy.ts | 50 ++++++- .../generate_many_dependencies.ts | 69 +++++++++ .../get_connection_stats/get_stats.ts | 140 ++++++++++-------- 4 files changed, 265 insertions(+), 61 deletions(-) create mode 100644 packages/kbn-apm-synthtrace/src/scenarios/service_many_dependencies.ts rename x-pack/plugins/apm/ftr_e2e/cypress/e2e/{ => dependencies}/dependencies.cy.ts (73%) create mode 100644 x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/generate_many_dependencies.ts diff --git a/packages/kbn-apm-synthtrace/src/scenarios/service_many_dependencies.ts b/packages/kbn-apm-synthtrace/src/scenarios/service_many_dependencies.ts new file mode 100644 index 0000000000000..a548e55f575a4 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/service_many_dependencies.ts @@ -0,0 +1,67 @@ +/* + * Copyright 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 { ApmFields, Instance } from '@kbn/apm-synthtrace-client'; +import { service } from '@kbn/apm-synthtrace-client/src/lib/apm/service'; +import { Scenario } from '../cli/scenario'; +import { RunOptions } from '../cli/utils/parse_run_cli_flags'; +import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; +import { withClient } from '../lib/utils/with_client'; + +const ENVIRONMENT = getSynthtraceEnvironment(__filename); +const MAX_DEPENDENCIES = 10000; +const MAX_DEPENDENCIES_PER_SERVICE = 500; +const MAX_SERVICES = 20; + +const scenario: Scenario = async (runOptions: RunOptions) => { + return { + generate: ({ range, clients: { apmEsClient } }) => { + const javaInstances = Array.from({ length: MAX_SERVICES }).map((_, index) => + service(`opbeans-java-${index}`, ENVIRONMENT, 'java').instance(`java-instance-${index}`) + ); + + const instanceDependencies = (instance: Instance, startIndex: number) => { + const rate = range.ratePerMinute(60); + + return rate.generator((timestamp, index) => { + const currentIndex = index % MAX_DEPENDENCIES_PER_SERVICE; + const destination = (startIndex + currentIndex) % MAX_DEPENDENCIES; + + const span = instance + .transaction({ transactionName: 'GET /java' }) + .timestamp(timestamp) + .duration(400) + .success() + .children( + instance + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) + .destination(`elasticsearch/${destination}`) + .timestamp(timestamp) + .duration(200) + .success() + ); + + return span; + }); + }; + + return withClient( + apmEsClient, + javaInstances.map((instance, index) => + instanceDependencies(instance, (index * MAX_DEPENDENCIES_PER_SERVICE) % MAX_DEPENDENCIES) + ) + ); + }, + }; +}; + +export default scenario; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts similarity index 73% rename from x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies.cy.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts index 3200ba846fc76..11741c3d3066b 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/dependencies.cy.ts @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { synthtrace } from '../../synthtrace'; -import { opbeans } from '../fixtures/synthtrace/opbeans'; -import { checkA11y } from '../support/commands'; +import { synthtrace } from '../../../synthtrace'; +import { opbeans } from '../../fixtures/synthtrace/opbeans'; +import { checkA11y } from '../../support/commands'; +import { generateManyDependencies } from './generate_many_dependencies'; const start = '2021-10-10T00:00:00.000Z'; const end = '2021-10-10T00:15:00.000Z'; @@ -120,3 +121,46 @@ describe('Dependencies', () => { }); }); }); + +describe('Dependencies with high volume of data', () => { + before(() => { + synthtrace.index( + generateManyDependencies({ + from: new Date(start).getTime(), + to: new Date(end).getTime(), + }) + ); + }); + + after(() => { + synthtrace.clean(); + }); + + beforeEach(() => { + cy.loginAsViewerUser(); + }); + + it('shows dependencies inventory page', () => { + cy.visitKibana( + `/app/apm/dependencies/inventory?${new URLSearchParams({ + ...timeRange, + kuery: 'elasticsearch*', + })}` + ); + + cy.getByTestSubj('dependenciesTable'); + cy.contains('nav', 'Page 1 of 60'); + }); + + it('shows service dependencies', () => { + cy.visitKibana( + `/app/apm/services/synth-java-0/dependencies?${new URLSearchParams({ + ...timeRange, + })}` + ); + + cy.getByTestSubj('serviceDependenciesBreakdownChart').get('canvas'); + cy.getByTestSubj('dependenciesTable'); + cy.contains('nav', 'Page 1 of 100'); + }); +}); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/generate_many_dependencies.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/generate_many_dependencies.ts new file mode 100644 index 0000000000000..0389cd7f90b2f --- /dev/null +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/dependencies/generate_many_dependencies.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { apm, Instance, timerange } from '@kbn/apm-synthtrace-client'; + +const MAX_DEPENDENCIES = 10000; +const MAX_DEPENDENCIES_PER_SERVICE = 500; +const MAX_SERVICES = 20; + +export function generateManyDependencies({ + from, + to, +}: { + from: number; + to: number; +}) { + const instances = Array.from({ length: MAX_SERVICES }).map((_, index) => + apm + .service({ + name: `synth-java-${index}`, + environment: 'production', + agentName: 'java', + }) + .instance(`java-instance-${index}`) + ); + + const instanceDependencies = (instance: Instance, startIndex: number) => { + return Array.from( + timerange(new Date(from), new Date(to)) + .interval('1m') + .rate(60) + .generator((timestamp, index) => { + const currentIndex = index % MAX_DEPENDENCIES_PER_SERVICE; + const destination = (startIndex + currentIndex) % MAX_DEPENDENCIES; + + const span = instance + .transaction({ transactionName: 'GET /java' }) + .timestamp(timestamp) + .duration(400) + .success() + .children( + instance + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) + .destination(`elasticsearch/${destination}`) + .timestamp(timestamp) + .duration(200) + .success() + ); + + return span; + }) + ); + }; + + return instances.flatMap((instance, index) => + instanceDependencies( + instance, + (index * MAX_DEPENDENCIES_PER_SERVICE) % MAX_DEPENDENCIES + ) + ); +} diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts index 4524d5121b78d..7dc038ef744c1 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts @@ -33,6 +33,7 @@ import { excludeRumExitSpansQuery } from '../exclude_rum_exit_spans_query'; import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; import { getDocumentTypeFilterForServiceDestinationStatistics } from '../../helpers/spans/get_is_using_service_destination_metrics'; +const MAX_ITEMS = 1500; export const getStats = async ({ apmEventClient, start, @@ -54,7 +55,85 @@ export const getStats = async ({ offset, }); - const response = await apmEventClient.search('get_connection_stats', { + const response = await getConnectionStats({ + apmEventClient, + startWithOffset, + endWithOffset, + filter, + numBuckets, + }); + + return ( + response.aggregations?.connections.buckets.map((bucket) => { + const sample = bucket.sample.top[0].metrics; + const serviceName = bucket.key.serviceName as string; + const dependencyName = bucket.key.dependencyName as string; + + return { + from: { + id: objectHash({ serviceName }), + serviceName, + environment: (sample[SERVICE_ENVIRONMENT] || + ENVIRONMENT_NOT_DEFINED.value) as string, + agentName: sample[AGENT_NAME] as AgentName, + type: NodeType.service as const, + }, + to: { + id: objectHash({ dependencyName }), + dependencyName, + spanType: sample[SPAN_TYPE] as string, + spanSubtype: (sample[SPAN_SUBTYPE] || '') as string, + type: NodeType.dependency as const, + }, + value: { + count: sum( + bucket.timeseries.buckets.map( + (dateBucket) => dateBucket.count.value ?? 0 + ) + ), + latency_sum: sum( + bucket.timeseries.buckets.map( + (dateBucket) => dateBucket.latency_sum.value ?? 0 + ) + ), + error_count: sum( + bucket.timeseries.buckets.flatMap( + (dateBucket) => + dateBucket[EVENT_OUTCOME].buckets.find( + (outcomeBucket) => outcomeBucket.key === EventOutcome.failure + )?.count.value ?? 0 + ) + ), + }, + timeseries: bucket.timeseries.buckets.map((dateBucket) => ({ + x: dateBucket.key + offsetInMs, + count: dateBucket.count.value ?? 0, + latency_sum: dateBucket.latency_sum.value ?? 0, + error_count: + dateBucket[EVENT_OUTCOME].buckets.find( + (outcomeBucket) => outcomeBucket.key === EventOutcome.failure + )?.count.value ?? 0, + })), + }; + }) ?? [] + ); +}; + +async function getConnectionStats({ + apmEventClient, + startWithOffset, + endWithOffset, + filter, + numBuckets, +}: { + apmEventClient: APMEventClient; + startWithOffset: number; + endWithOffset: number; + filter: QueryDslQueryContainer[]; + numBuckets: number; + after?: { serviceName: string | number; dependencyName: string | number }; +}) { + return apmEventClient.search('get_connection_stats', { apm: { sources: [ { @@ -79,7 +158,7 @@ export const getStats = async ({ aggs: { connections: { composite: { - size: 10000, + size: MAX_ITEMS, sources: asMutableArray([ { serviceName: { @@ -174,59 +253,4 @@ export const getStats = async ({ }, }, }); - - return ( - response.aggregations?.connections.buckets.map((bucket) => { - const sample = bucket.sample.top[0].metrics; - const serviceName = bucket.key.serviceName as string; - const dependencyName = bucket.key.dependencyName as string; - - return { - from: { - id: objectHash({ serviceName }), - serviceName, - environment: (sample[SERVICE_ENVIRONMENT] || - ENVIRONMENT_NOT_DEFINED.value) as string, - agentName: sample[AGENT_NAME] as AgentName, - type: NodeType.service as const, - }, - to: { - id: objectHash({ dependencyName }), - dependencyName, - spanType: sample[SPAN_TYPE] as string, - spanSubtype: (sample[SPAN_SUBTYPE] || '') as string, - type: NodeType.dependency as const, - }, - value: { - count: sum( - bucket.timeseries.buckets.map( - (dateBucket) => dateBucket.count.value ?? 0 - ) - ), - latency_sum: sum( - bucket.timeseries.buckets.map( - (dateBucket) => dateBucket.latency_sum.value ?? 0 - ) - ), - error_count: sum( - bucket.timeseries.buckets.flatMap( - (dateBucket) => - dateBucket[EVENT_OUTCOME].buckets.find( - (outcomeBucket) => outcomeBucket.key === EventOutcome.failure - )?.count.value ?? 0 - ) - ), - }, - timeseries: bucket.timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key + offsetInMs, - count: dateBucket.count.value ?? 0, - latency_sum: dateBucket.latency_sum.value ?? 0, - error_count: - dateBucket[EVENT_OUTCOME].buckets.find( - (outcomeBucket) => outcomeBucket.key === EventOutcome.failure - )?.count.value ?? 0, - })), - }; - }) ?? [] - ); -}; +} From 71db3d93e767645767c6df2d1b269fb83e7961c8 Mon Sep 17 00:00:00 2001 From: Katerina Date: Fri, 5 Jan 2024 13:58:46 +0200 Subject: [PATCH 04/10] [APM] Enable dashboard tab in mobile template (#174255) closes https://github.com/elastic/kibana/issues/174251 it should be consistent with the backend services. image --- .../actions/edit_dashboard.tsx | 3 +++ .../actions/link_dashboard.tsx | 3 +++ .../actions/save_dashboard_modal.tsx | 7 ++----- .../app/service_dashboards/index.tsx | 15 ++++++++++++--- .../routing/mobile_service_detail/index.tsx | 16 +++++++++++++++- .../mobile_service_template/index.tsx | 18 ++++++++++++++++-- 6 files changed, 51 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_dashboards/actions/edit_dashboard.tsx b/x-pack/plugins/apm/public/components/app/service_dashboards/actions/edit_dashboard.tsx index e3a6619b446d6..c880a912a5b51 100644 --- a/x-pack/plugins/apm/public/components/app/service_dashboards/actions/edit_dashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/service_dashboards/actions/edit_dashboard.tsx @@ -13,9 +13,11 @@ import { MergedServiceDashboard } from '..'; export function EditDashboard({ onRefresh, currentDashboard, + serviceName, }: { onRefresh: () => void; currentDashboard: MergedServiceDashboard; + serviceName: string; }) { const [isModalVisible, setIsModalVisible] = useState(false); return ( @@ -37,6 +39,7 @@ export function EditDashboard({ onClose={() => setIsModalVisible(!isModalVisible)} onRefresh={onRefresh} currentDashboard={currentDashboard} + serviceName={serviceName} /> )} diff --git a/x-pack/plugins/apm/public/components/app/service_dashboards/actions/link_dashboard.tsx b/x-pack/plugins/apm/public/components/app/service_dashboards/actions/link_dashboard.tsx index 7b652c21039d8..0db2654c1d66b 100644 --- a/x-pack/plugins/apm/public/components/app/service_dashboards/actions/link_dashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/service_dashboards/actions/link_dashboard.tsx @@ -14,10 +14,12 @@ export function LinkDashboard({ onRefresh, emptyButton = false, serviceDashboards, + serviceName, }: { onRefresh: () => void; emptyButton?: boolean; serviceDashboards?: MergedServiceDashboard[]; + serviceName: string; }) { const [isModalVisible, setIsModalVisible] = useState(false); @@ -51,6 +53,7 @@ export function LinkDashboard({ onClose={() => setIsModalVisible(false)} onRefresh={onRefresh} serviceDashboards={serviceDashboards} + serviceName={serviceName} /> )} diff --git a/x-pack/plugins/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx b/x-pack/plugins/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx index 3083e41c2dac2..6872b0d2cc805 100644 --- a/x-pack/plugins/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx +++ b/x-pack/plugins/apm/public/components/app/service_dashboards/actions/save_dashboard_modal.tsx @@ -28,7 +28,6 @@ import { callApmApi } from '../../../../services/rest/create_call_apm_api'; import { useDashboardFetcher } from '../../../../hooks/use_dashboards_fetcher'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { useApmParams } from '../../../../hooks/use_apm_params'; import { SERVICE_NAME } from '../../../../../common/es_fields/apm'; import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { MergedServiceDashboard } from '..'; @@ -38,6 +37,7 @@ interface Props { onRefresh: () => void; currentDashboard?: MergedServiceDashboard; serviceDashboards?: MergedServiceDashboard[]; + serviceName: string; } export function SaveDashboardModal({ @@ -45,6 +45,7 @@ export function SaveDashboardModal({ onRefresh, currentDashboard, serviceDashboards, + serviceName, }: Props) { const { core: { notifications }, @@ -71,10 +72,6 @@ export function SaveDashboardModal({ const isEditMode = !!currentDashboard?.id; - const { - path: { serviceName }, - } = useApmParams('/services/{serviceName}/dashboards'); - const reloadCustomDashboards = useCallback(() => { onRefresh(); }, [onRefresh]); diff --git a/x-pack/plugins/apm/public/components/app/service_dashboards/index.tsx b/x-pack/plugins/apm/public/components/app/service_dashboards/index.tsx index fe44d62c2388d..7426ec382b87f 100644 --- a/x-pack/plugins/apm/public/components/app/service_dashboards/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_dashboards/index.tsx @@ -28,7 +28,7 @@ import { SerializableRecord } from '@kbn/utility-types'; import { EmptyDashboards } from './empty_dashboards'; import { GotoDashboard, LinkDashboard } from './actions'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; -import { useApmParams } from '../../../hooks/use_apm_params'; +import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { SavedApmCustomDashboard } from '../../../../common/custom_dashboards'; import { ContextMenu } from './context_menu'; import { UnlinkDashboard } from './actions/unlink_dashboard'; @@ -49,7 +49,10 @@ export function ServiceDashboards() { const { path: { serviceName }, query: { environment, kuery, rangeFrom, rangeTo, dashboardId }, - } = useApmParams('/services/{serviceName}/dashboards'); + } = useAnyOfApmParams( + '/services/{serviceName}/dashboards', + '/mobile-services/{serviceName}/dashboards' + ); const [dashboard, setDashboard] = useState(); const [serviceDashboards, setServiceDashboards] = useState< MergedServiceDashboard[] @@ -209,11 +212,13 @@ export function ServiceDashboards() { emptyButton={true} onRefresh={refetch} serviceDashboards={serviceDashboards} + serviceName={serviceName} />, , , ) : ( - } /> + + } + /> )} ); diff --git a/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx index 11a365e0d8328..fa8e7ee0323ab 100644 --- a/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx @@ -26,7 +26,7 @@ import { ErrorGroupDetails } from '../../app/mobile/errors_and_crashes_group_det import { CrashGroupDetails } from '../../app/mobile/errors_and_crashes_group_details/crash_group_details'; import { MobileErrorCrashesOverview } from '../../app/mobile/errors_and_crashes_overview'; import { ServiceDependencies } from '../../app/service_dependencies'; - +import { ServiceDashboards } from '../../app/service_dashboards'; export function page({ title, tabKey, @@ -270,6 +270,20 @@ export const mobileServiceDetailRoute = { }), }), }, + '/mobile-services/{serviceName}/dashboards': { + ...page({ + tabKey: 'dashboards', + title: i18n.translate('xpack.apm.views.dashboard.title', { + defaultMessage: 'Dashboards', + }), + element: , + }), + params: t.partial({ + query: t.partial({ + dashboardId: t.string, + }), + }), + }, '/mobile-services/{serviceName}/': { element: , }, diff --git a/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx index 3c9bde66ff3ac..b5ce69d9c1d03 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/mobile_service_template/index.tsx @@ -35,7 +35,8 @@ type Tab = NonNullable[0] & { | 'dependencies' | 'errors-and-crashes' | 'service-map' - | 'alerts'; + | 'alerts' + | 'dashboards'; hidden?: boolean; }; @@ -231,12 +232,25 @@ function useTabs({ selectedTabKey }: { selectedTabKey: Tab['key'] }) { path: { serviceName }, query, }), - append: , label: i18n.translate('xpack.apm.mobileServiceDetails.alertsTabLabel', { defaultMessage: 'Alerts', }), hidden: !(isAlertingAvailable && canReadAlerts), }, + { + key: 'dashboards', + href: router.link('/mobile-services/{serviceName}/dashboards', { + path: { serviceName }, + query, + }), + append: , + label: i18n.translate( + 'xpack.apm.mobileServiceDetails.dashboardsTabLabel', + { + defaultMessage: 'Dashboards', + } + ), + }, ]; return tabs From c39fac616b7413cc21fe17838025a443e60fdc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Fri, 5 Jan 2024 08:01:59 -0500 Subject: [PATCH 05/10] Rename connector compatibility for Generative AI so it is split between security and o11y (#174000) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this PR, I'm renaming `Generative AI` to `Generative AI for Security` in the connectors comatibility list so we have a split on Gen AI for Security and Observability (follow up from https://github.com/elastic/kibana/pull/173826). ## Screenshots Screenshot 2024-01-03 at 11 53 00 AM Screenshot 2024-01-03 at 11 53 32 AM Screenshot 2024-01-03 at 11 53 39 AM ## To verify **Connectors** 1. Startup Kibana in trial mode 2. Open the create Bedrock connector flyout from the connectors page 3. Notice the compatibility is only for Security 4. Create a Bedrock connector (input random text in all fields to pass validation) 5. Open the create OpenAI connector from the connectors page 6. Notice the compatibility is for Security and Observability 7. Create an OpenAI connector (input random text in all fields to pass validation) **Security Solution** 9. Navigate to the Security solution (`/app/security/get_started`) 10. Open the AI Assistant on the top right 11. Open the `Conversation Settings` 12. See OpenAI and Bedrock connectors displaying **Observability** 13. Navigate to the Observability app (`/app/observability/overview`) 14. Open the AI Assistant on the top right 15. Select the actions menu on the top right of the flyout and open `AI Assistant Settings` 16. Open the default connector dropdown 17. Notice only OpenAI connectors displaying --- .../use_load_action_types/index.test.tsx | 2 +- .../use_load_action_types/index.tsx | 4 ++-- .../common/connector_feature_config.test.ts | 6 +++--- .../common/connector_feature_config.ts | 20 +++++++++---------- x-pack/plugins/actions/common/types.ts | 2 +- .../server/connector_types/bedrock/index.ts | 4 ++-- .../server/connector_types/openai/index.ts | 4 ++-- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 10 files changed, 21 insertions(+), 24 deletions(-) diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.test.tsx index 0aa6ed21e2f88..50d3a73797413 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.test.tsx @@ -37,7 +37,7 @@ describe('useLoadActionTypes', () => { await waitForNextUpdate(); expect(defaultProps.http.get).toHaveBeenCalledWith('/api/actions/connector_types', { - query: { feature_id: 'generativeAI' }, + query: { feature_id: 'generativeAIForSecurity' }, }); expect(toasts.addError).not.toHaveBeenCalled(); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.tsx index d307f85e43b7f..cf62127f286ca 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_action_types/index.tsx @@ -15,7 +15,7 @@ import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { ActionType } from '@kbn/actions-plugin/common'; import { HttpSetup } from '@kbn/core-http-browser'; import { IToasts } from '@kbn/core-notifications-browser'; -import { GenerativeAIConnectorFeatureId } from '@kbn/actions-plugin/common'; +import { GenerativeAIForSecurityConnectorFeatureId } from '@kbn/actions-plugin/common'; import * as i18n from '../translations'; /** @@ -39,7 +39,7 @@ export const useLoadActionTypes = ({ async () => { const queryResult = await loadActionTypes({ http, - featureId: GenerativeAIConnectorFeatureId, + featureId: GenerativeAIForSecurityConnectorFeatureId, }); const sortedData = queryResult.sort((a, b) => a.name.localeCompare(b.name)); diff --git a/x-pack/plugins/actions/common/connector_feature_config.test.ts b/x-pack/plugins/actions/common/connector_feature_config.test.ts index c12e3db21127f..1f18e923593d5 100644 --- a/x-pack/plugins/actions/common/connector_feature_config.test.ts +++ b/x-pack/plugins/actions/common/connector_feature_config.test.ts @@ -13,7 +13,7 @@ import { describe('areValidFeatures', () => { it('returns true when all inputs are valid features', () => { - expect(areValidFeatures(['alerting', 'cases', 'generativeAI'])).toBeTruthy(); + expect(areValidFeatures(['alerting', 'cases', 'generativeAIForSecurity'])).toBeTruthy(); }); it('returns true when only one input and it is a valid feature', () => { @@ -43,8 +43,8 @@ describe('getConnectorFeatureName', () => { describe('getConnectorCompatibility', () => { it('returns the compatibility list for valid feature ids', () => { expect( - getConnectorCompatibility(['alerting', 'cases', 'uptime', 'siem', 'generativeAI']) - ).toEqual(['Alerting Rules', 'Cases', 'Generative AI']); + getConnectorCompatibility(['alerting', 'cases', 'uptime', 'siem', 'generativeAIForSecurity']) + ).toEqual(['Alerting Rules', 'Cases', 'Generative AI for Security']); }); it('skips invalid feature ids', () => { diff --git a/x-pack/plugins/actions/common/connector_feature_config.ts b/x-pack/plugins/actions/common/connector_feature_config.ts index fb61ff35da6b7..4638387d31c5e 100644 --- a/x-pack/plugins/actions/common/connector_feature_config.ts +++ b/x-pack/plugins/actions/common/connector_feature_config.ts @@ -25,20 +25,20 @@ export const AlertingConnectorFeatureId = 'alerting'; export const CasesConnectorFeatureId = 'cases'; export const UptimeConnectorFeatureId = 'uptime'; export const SecurityConnectorFeatureId = 'siem'; -export const GenerativeAIConnectorFeatureId = 'generativeAI'; +export const GenerativeAIForSecurityConnectorFeatureId = 'generativeAIForSecurity'; export const GenerativeAIForObservabilityConnectorFeatureId = 'generativeAIForObservability'; -const compatibilityGenerativeAI = i18n.translate( - 'xpack.actions.availableConnectorFeatures.compatibility.generativeAI', +const compatibilityGenerativeAIForSecurity = i18n.translate( + 'xpack.actions.availableConnectorFeatures.compatibility.generativeAIForSecurity', { - defaultMessage: 'Generative AI', + defaultMessage: 'Generative AI for Security', } ); const compatibilityGenerativeAIForObservability = i18n.translate( 'xpack.actions.availableConnectorFeatures.compatibility.generativeAIForObservability', { - defaultMessage: 'Generative AI For Observability', + defaultMessage: 'Generative AI for Observability', } ); @@ -88,10 +88,10 @@ export const SecuritySolutionFeature: ConnectorFeatureConfig = { compatibility: compatibilityAlertingRules, }; -export const GenerativeAIFeature: ConnectorFeatureConfig = { - id: GenerativeAIConnectorFeatureId, - name: compatibilityGenerativeAI, - compatibility: compatibilityGenerativeAI, +export const GenerativeAIForSecurityFeature: ConnectorFeatureConfig = { + id: GenerativeAIForSecurityConnectorFeatureId, + name: compatibilityGenerativeAIForSecurity, + compatibility: compatibilityGenerativeAIForSecurity, }; export const GenerativeAIForObservabilityFeature: ConnectorFeatureConfig = { @@ -105,7 +105,7 @@ const AllAvailableConnectorFeatures = { [CasesConnectorFeature.id]: CasesConnectorFeature, [UptimeConnectorFeature.id]: UptimeConnectorFeature, [SecuritySolutionFeature.id]: SecuritySolutionFeature, - [GenerativeAIFeature.id]: GenerativeAIFeature, + [GenerativeAIForSecurityFeature.id]: GenerativeAIForSecurityFeature, [GenerativeAIForObservabilityFeature.id]: GenerativeAIForObservabilityFeature, }; diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 2560cbae3f7e7..7420240d71cc0 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -13,7 +13,7 @@ export { CasesConnectorFeatureId, UptimeConnectorFeatureId, SecurityConnectorFeatureId, - GenerativeAIConnectorFeatureId, + GenerativeAIForSecurityConnectorFeatureId, } from './connector_feature_config'; export interface ActionType { id: string; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts index e9ab583277282..5f295b8c39367 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/index.ts @@ -10,7 +10,7 @@ import { SubActionConnectorType, ValidatorType, } from '@kbn/actions-plugin/server/sub_action_framework/types'; -import { GenerativeAIConnectorFeatureId } from '@kbn/actions-plugin/common'; +import { GenerativeAIForSecurityConnectorFeatureId } from '@kbn/actions-plugin/common'; import { urlAllowListValidator } from '@kbn/actions-plugin/server'; import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { assertURL } from '@kbn/actions-plugin/server/sub_action_framework/helpers/validators'; @@ -29,7 +29,7 @@ export const getConnectorType = (): SubActionConnectorType => ( secrets: SecretsSchema, }, validators: [{ type: ValidatorType.CONFIG, validator: configValidator }], - supportedFeatureIds: [GenerativeAIConnectorFeatureId], + supportedFeatureIds: [GenerativeAIForSecurityConnectorFeatureId], minimumLicenseRequired: 'enterprise' as const, renderParameterTemplates, }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/index.ts index 6852a83a9ee3f..3fa865209ba5c 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/index.ts @@ -11,7 +11,7 @@ import { ValidatorType, } from '@kbn/actions-plugin/server/sub_action_framework/types'; import { - GenerativeAIConnectorFeatureId, + GenerativeAIForSecurityConnectorFeatureId, GenerativeAIForObservabilityConnectorFeatureId, } from '@kbn/actions-plugin/common'; import { urlAllowListValidator } from '@kbn/actions-plugin/server'; @@ -37,7 +37,7 @@ export const getConnectorType = (): SubActionConnectorType => ( }, validators: [{ type: ValidatorType.CONFIG, validator: configValidator }], supportedFeatureIds: [ - GenerativeAIConnectorFeatureId, + GenerativeAIForSecurityConnectorFeatureId, GenerativeAIForObservabilityConnectorFeatureId, ], minimumLicenseRequired: 'enterprise' as const, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e7fa92aa02a3c..e8365d42168ae 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -7621,7 +7621,6 @@ "xpack.actions.availableConnectorFeatures.cases": "Cas", "xpack.actions.availableConnectorFeatures.compatibility.alertingRules": "Règles d'alerting", "xpack.actions.availableConnectorFeatures.compatibility.cases": "Cas", - "xpack.actions.availableConnectorFeatures.compatibility.generativeAI": "IA générative", "xpack.actions.availableConnectorFeatures.securitySolution": "Solution de sécurité", "xpack.actions.availableConnectorFeatures.uptime": "Uptime", "xpack.actions.builtin.cases.jiraTitle": "Jira", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4a23239256f53..bf36677dd19e6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7636,7 +7636,6 @@ "xpack.actions.availableConnectorFeatures.cases": "ケース", "xpack.actions.availableConnectorFeatures.compatibility.alertingRules": "アラートルール", "xpack.actions.availableConnectorFeatures.compatibility.cases": "ケース", - "xpack.actions.availableConnectorFeatures.compatibility.generativeAI": "生成AI", "xpack.actions.availableConnectorFeatures.securitySolution": "セキュリティソリューション", "xpack.actions.availableConnectorFeatures.uptime": "アップタイム", "xpack.actions.builtin.cases.jiraTitle": "Jira", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ccce90db35aaf..ebc2118a79b70 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7702,7 +7702,6 @@ "xpack.actions.availableConnectorFeatures.cases": "案例", "xpack.actions.availableConnectorFeatures.compatibility.alertingRules": "告警规则", "xpack.actions.availableConnectorFeatures.compatibility.cases": "案例", - "xpack.actions.availableConnectorFeatures.compatibility.generativeAI": "生成式 AI", "xpack.actions.availableConnectorFeatures.securitySolution": "安全解决方案", "xpack.actions.availableConnectorFeatures.uptime": "运行时间", "xpack.actions.builtin.cases.jiraTitle": "Jira", From b37634d2b70c6ae99a1b3da9f8a529c112f557ef Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 5 Jan 2024 15:19:14 +0200 Subject: [PATCH 06/10] [Lens][Inline editing] Improve the api for the embeddable consumers (#173841) ## Summary Closes https://github.com/elastic/kibana/issues/167632 This PR provides a simpler api for the Lens embeddable consumers who want to provide inline editing capabilities. I added an example to help with the integration. Run kibana with ``` yarn start --run-examples ``` http://localhost:5601/app/lens_embeddable_inline_editing_example image image It also allows the consumers to render the inline editing component in a custom element in case you don't want to open a push flyout. ![custom-container](https://github.com/elastic/kibana/assets/17003240/6ce1b9c6-dab0-4321-b4c0-ae196dfb4a84) I included a readme on how to use the api. ### Note This is the first PR which uses the new Lens config builder so some of the changes are not related to the api improvements but they are fixing some bugs on the builder. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + .../lens_attributes_builder.test.ts | 46 ++--- .../config_builder/charts/gauge.test.ts | 3 - .../config_builder/charts/heatmap.test.ts | 3 - .../config_builder/charts/metric.test.ts | 3 - .../config_builder/charts/partition.test.ts | 3 - .../config_builder/charts/region_map.test.ts | 3 - .../config_builder/charts/table.test.ts | 3 - .../config_builder/charts/tag_cloud.test.ts | 3 - .../config_builder/charts/xy.test.ts | 3 - .../config_builder/utils.test.ts | 3 - .../config_builder/utils.ts | 14 +- packages/kbn-optimizer/limits.yml | 2 +- .../group_preview.test.tsx | 2 +- .../group_editor_flyout/lens_attributes.ts | 2 +- tsconfig.base.json | 2 + .../.eslintrc.json | 5 + .../README.md | 68 +++++++ .../kibana.jsonc | 21 ++ .../public/app.tsx | 182 ++++++++++++++++++ .../public/embeddable.tsx | 161 ++++++++++++++++ .../public/flyout.tsx | 124 ++++++++++++ .../public/image.png | Bin 0 -> 155151 bytes .../public/index.ts | 10 + .../public/mount.tsx | 49 +++++ .../public/plugin.ts | 57 ++++++ .../public/utils.ts | 63 ++++++ .../tsconfig.json | 26 +++ .../configurations/lens_attributes.test.ts | 2 +- .../get_edit_lens_configuration.tsx | 4 + .../lens_configuration_flyout.tsx | 6 + .../shared/edit_on_the_fly/types.ts | 4 + x-pack/plugins/lens/public/async_services.ts | 1 + .../embeddable/embeddable_component.tsx | 2 +- x-pack/plugins/lens/public/index.ts | 2 + x-pack/plugins/lens/public/plugin.ts | 18 ++ .../in_app_embeddable_edit_action.test.tsx | 97 ++++++++++ .../in_app_embeddable_edit_action.tsx | 64 ++++++ .../in_app_embeddable_edit_action_helpers.tsx | 154 +++++++++++++++ .../in_app_embeddable_edit_trigger.ts | 19 ++ .../in_app_embeddable_edit/types.ts | 37 ++++ x-pack/plugins/lens/readme.md | 8 + .../risk_summary_flyout/risk_summary.test.tsx | 8 +- yarn.lock | 3 + 45 files changed, 1223 insertions(+), 69 deletions(-) create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/.eslintrc.json create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/README.md create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/kibana.jsonc create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/flyout.tsx create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/image.png create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/index.ts create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/plugin.ts create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/public/utils.ts create mode 100644 x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_trigger.ts create mode 100644 x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b3d2e9cf82ad4..4dae217dce6ad 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -487,6 +487,7 @@ x-pack/plugins/kubernetes_security @elastic/kibana-cloud-security-posture packages/kbn-language-documentation-popover @elastic/kibana-visualizations packages/kbn-lens-embeddable-utils @elastic/obs-ux-infra_services-team @elastic/kibana-visualizations packages/kbn-lens-formula-docs @elastic/kibana-visualizations +x-pack/examples/lens_embeddable_inline_editing_example @elastic/kibana-visualizations x-pack/plugins/lens @elastic/kibana-visualizations x-pack/plugins/license_api_guard @elastic/platform-deployment-management x-pack/plugins/license_management @elastic/platform-deployment-management diff --git a/package.json b/package.json index 4f6aef817d579..0a4d9ebfcebdb 100644 --- a/package.json +++ b/package.json @@ -509,6 +509,7 @@ "@kbn/language-documentation-popover": "link:packages/kbn-language-documentation-popover", "@kbn/lens-embeddable-utils": "link:packages/kbn-lens-embeddable-utils", "@kbn/lens-formula-docs": "link:packages/kbn-lens-formula-docs", + "@kbn/lens-inline-editing-example-plugin": "link:x-pack/examples/lens_embeddable_inline_editing_example", "@kbn/lens-plugin": "link:x-pack/plugins/lens", "@kbn/license-api-guard-plugin": "link:x-pack/plugins/license_api_guard", "@kbn/license-management-plugin": "link:x-pack/plugins/license_management", diff --git a/packages/kbn-lens-embeddable-utils/attribute_builder/lens_attributes_builder.test.ts b/packages/kbn-lens-embeddable-utils/attribute_builder/lens_attributes_builder.test.ts index 199379e74eb9d..2f90e03099350 100644 --- a/packages/kbn-lens-embeddable-utils/attribute_builder/lens_attributes_builder.test.ts +++ b/packages/kbn-lens-embeddable-utils/attribute_builder/lens_attributes_builder.test.ts @@ -113,15 +113,13 @@ describe('lens_attributes_builder', () => { formulaAPI, }); const builder = new LensAttributesBuilder({ visualization: metriChart }); + const { - state: { - datasourceStates: { - formBased: { layers }, - }, - visualization, - }, + state: { datasourceStates: datasourceStates, visualization }, } = builder.build(); + const layers = datasourceStates.formBased?.layers; + expect(layers).toEqual({ layer: { columnOrder: ['metric_formula_accessor'], @@ -156,14 +154,11 @@ describe('lens_attributes_builder', () => { }); const builder = new LensAttributesBuilder({ visualization: metriChart }); const { - state: { - datasourceStates: { - formBased: { layers }, - }, - visualization, - }, + state: { datasourceStates: datasourceStates, visualization }, } = builder.build(); + const layers = datasourceStates.formBased?.layers; + expect(layers).toEqual({ layer: { columnOrder: ['metric_formula_accessor'], @@ -215,14 +210,11 @@ describe('lens_attributes_builder', () => { }); const builder = new LensAttributesBuilder({ visualization: xyChart }); const { - state: { - datasourceStates: { - formBased: { layers }, - }, - visualization, - }, + state: { datasourceStates: datasourceStates, visualization }, } = builder.build(); + const layers = datasourceStates.formBased?.layers; + expect(layers).toEqual({ layer_0: { columnOrder: ['x_date_histogram', 'formula_accessor_0_0'], @@ -272,14 +264,11 @@ describe('lens_attributes_builder', () => { }); const builder = new LensAttributesBuilder({ visualization: xyChart }); const { - state: { - datasourceStates: { - formBased: { layers }, - }, - visualization, - }, + state: { datasourceStates: datasourceStates, visualization }, } = builder.build(); + const layers = datasourceStates.formBased?.layers; + expect(layers).toEqual({ layer_0: { columnOrder: ['x_date_histogram', 'formula_accessor_0_0'], @@ -340,14 +329,11 @@ describe('lens_attributes_builder', () => { }); const builder = new LensAttributesBuilder({ visualization: xyChart }); const { - state: { - datasourceStates: { - formBased: { layers }, - }, - visualization, - }, + state: { datasourceStates: datasourceStates, visualization }, } = builder.build(); + const layers = datasourceStates.formBased?.layers; + expect(layers).toEqual({ layer_0: { columnOrder: ['x_date_histogram', 'formula_accessor_0_0', 'formula_accessor_0_1'], diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/gauge.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/gauge.test.ts index 5e0e5dc1de2a7..aa13813e37b82 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/gauge.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/gauge.test.ts @@ -79,9 +79,6 @@ test('generates gauge chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/heatmap.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/heatmap.test.ts index cfcabb131b451..91786a0b91e23 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/heatmap.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/heatmap.test.ts @@ -81,9 +81,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/metric.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/metric.test.ts index cf82d3a2f5f4d..86078332ebb42 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/metric.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/metric.test.ts @@ -79,9 +79,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/partition.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/partition.test.ts index 22ee18f3e5f3e..120b3069552d8 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/partition.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/partition.test.ts @@ -80,9 +80,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/region_map.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/region_map.test.ts index 0093b03e33bc3..4fd97797b69dd 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/region_map.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/region_map.test.ts @@ -80,9 +80,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/table.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/table.test.ts index 909f3e737ac99..dbe67d15416c4 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/table.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/table.test.ts @@ -80,9 +80,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/tag_cloud.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/tag_cloud.test.ts index 08885ba57ba83..fe94e8f789d43 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/tag_cloud.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/tag_cloud.test.ts @@ -80,9 +80,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/charts/xy.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/charts/xy.test.ts index d1646b4c0ec7d..42dc00dc2ba26 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/charts/xy.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/charts/xy.test.ts @@ -87,9 +87,6 @@ test('generates metric chart config', async () => { "test": Object {}, }, "datasourceStates": Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/utils.test.ts b/packages/kbn-lens-embeddable-utils/config_builder/utils.test.ts index 7709c6969b29f..b4b7c7ddc4842 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/utils.test.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/utils.test.ts @@ -199,9 +199,6 @@ describe('buildDatasourceStates', () => { ); expect(results).toMatchInlineSnapshot(` Object { - "formBased": Object { - "layers": Object {}, - }, "textBased": Object { "layers": Object { "layer_0": Object { diff --git a/packages/kbn-lens-embeddable-utils/config_builder/utils.ts b/packages/kbn-lens-embeddable-utils/config_builder/utils.ts index 21c48ca76a52b..78434f8dbb10f 100644 --- a/packages/kbn-lens-embeddable-utils/config_builder/utils.ts +++ b/packages/kbn-lens-embeddable-utils/config_builder/utils.ts @@ -191,10 +191,7 @@ export const buildDatasourceStates = async ( getValueColumns: (config: any, i: number) => TextBasedLayerColumn[], dataViewsAPI: DataViewsPublicPluginStart ) => { - const layers: LensAttributes['state']['datasourceStates'] = { - textBased: { layers: {} }, - formBased: { layers: {} }, - }; + let layers: Partial = {}; const mainDataset = config.dataset; const configLayers = 'layers' in config ? config.layers : [config]; @@ -226,7 +223,14 @@ export const buildDatasourceStates = async ( getValueColumns ); if (layerConfig) { - layers[type]!.layers[layerId] = layerConfig; + layers = { + ...layers, + [type]: { + layers: { + [layerId]: layerConfig, + }, + }, + }; } } } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index d38e1399ed3aa..9c96bfe371dfa 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -86,7 +86,7 @@ pageLoadAssetSize: kibanaUsageCollection: 16463 kibanaUtils: 79713 kubernetesSecurity: 77234 - lens: 41000 + lens: 42000 licenseManagement: 41817 licensing: 29004 links: 44490 diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx index 09c7c5a265355..386976ac571b6 100644 --- a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx @@ -237,7 +237,7 @@ describe('group editor preview', () => { const assertTimeField = (fieldName: string, attributes: TypedLensByValueInput['attributes']) => expect( ( - attributes.state.datasourceStates.formBased.layers[DATA_LAYER_ID].columns[ + attributes.state.datasourceStates.formBased?.layers[DATA_LAYER_ID].columns[ DATE_HISTOGRAM_COLUMN_ID ] as FieldBasedIndexPatternColumn ).sourceField diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts index 7943b1d905b6a..f9e56ded409b9 100644 --- a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/lens_attributes.ts @@ -146,7 +146,7 @@ export const getLensAttributes = (group: EventAnnotationGroupConfig, timeField: export const getCurrentTimeField = (attributes: TypedLensByValueInput['attributes']) => { return ( - attributes.state.datasourceStates.formBased.layers[DATA_LAYER_ID].columns[ + attributes.state.datasourceStates?.formBased?.layers[DATA_LAYER_ID].columns[ DATE_HISTOGRAM_COLUMN_ID ] as FieldBasedIndexPatternColumn ).sourceField; diff --git a/tsconfig.base.json b/tsconfig.base.json index d241aace37840..78c48a4acaff8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -968,6 +968,8 @@ "@kbn/lens-embeddable-utils/*": ["packages/kbn-lens-embeddable-utils/*"], "@kbn/lens-formula-docs": ["packages/kbn-lens-formula-docs"], "@kbn/lens-formula-docs/*": ["packages/kbn-lens-formula-docs/*"], + "@kbn/lens-inline-editing-example-plugin": ["x-pack/examples/lens_embeddable_inline_editing_example"], + "@kbn/lens-inline-editing-example-plugin/*": ["x-pack/examples/lens_embeddable_inline_editing_example/*"], "@kbn/lens-plugin": ["x-pack/plugins/lens"], "@kbn/lens-plugin/*": ["x-pack/plugins/lens/*"], "@kbn/license-api-guard-plugin": ["x-pack/plugins/license_api_guard"], diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/.eslintrc.json b/x-pack/examples/lens_embeddable_inline_editing_example/.eslintrc.json new file mode 100644 index 0000000000000..2aab6c2d9093b --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/consistent-type-definitions": 0 + } +} diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/README.md b/x-pack/examples/lens_embeddable_inline_editing_example/README.md new file mode 100644 index 0000000000000..ae280f9a4c17e --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/README.md @@ -0,0 +1,68 @@ +# Inline editing of Lens embeddable + +To run this example plugin, use the command `yarn start --run-examples`. + +This plugin contains examples on how to integrate the inline editing capabilities to your Lens embeddable. + +Steps: + * Add UIActions on your start dependencies + * On your embeddable use the onLoad callback to store in the local state the adapters and lensEmbeddableOutput$. + +```tsx + // my Lens embeddable + +``` + +```tsx + const [lensLoadEvent, setLensLoadEvent] = useState(null); + // my onLoad callback + const onLoad = useCallback( + ( + isLoading: boolean, + adapters: LensChartLoadEvent['adapters'] | undefined, + lensEmbeddableOutput$?: LensChartLoadEvent['embeddableOutput$'] + ) => { + const adapterTables = adapters?.tables?.tables; + if (adapterTables && !isLoading) { + setLensLoadEvent({ + adapters, + embeddableOutput$: lensEmbeddableOutput$, + }); + } + }, + [] + ); +``` + * Create the triggerOptions. You will need: + - The attributes of the lens embeddable input + - The lensChartEvent that you retrieved from the onLoad callback + - An onUpdate callback to update the embeddable input with the new attributes + - An optional onApply callback if you want to add an action when the Apply button is clicked + - An optional onCancel callback if you want to add an action when the Cancel button is clicked + +```tsx + // my trigger options + const triggerOptions = { + attributes: embeddableInput?.attributes, + lensEvent: lensLoadEvent, + onUpdate: (newAttributes: TypedLensByValueInput['attributes']) => { + // onUpdate I need to update my embeddableInput.attributes + }, + onApply: () => { + // optional callback when Apply button is clicked + }, + onCancel: () => { + // optional callback when Cancel button is clicked + }, + }; +``` + +* Add a button which will open the editing flyout. Execute the IN_APP_EMBEDDABLE_EDIT_TRIGGER trigger onClick +```tsx +uiActions.getTrigger('IN_APP_EMBEDDABLE_EDIT_TRIGGER').exec(triggerOptions); +``` + +### Important note +In case you don't want to open a push flyout you can pass an html element to the triggerOptions, the `container` property. This is going +to render the inline editing component to this container element. In that case you will need an extra handling on your side. +Check the 3rd example for implementation details. \ No newline at end of file diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/kibana.jsonc b/x-pack/examples/lens_embeddable_inline_editing_example/kibana.jsonc new file mode 100644 index 0000000000000..01540fe2de3a1 --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/kibana.jsonc @@ -0,0 +1,21 @@ +{ + "type": "plugin", + "id": "@kbn/lens-inline-editing-example-plugin", + "owner": "@elastic/kibana-visualizations", + "plugin": { + "id": "inlineEditingLensExample", + "server": false, + "browser": true, + "configPath": [ + "lens_embeddable_inline_editing_example" + ], + "requiredPlugins": [ + "uiActions", + "lens", + "dataViews", + "embeddable", + "developerExamples", + "kibanaReact" + ] + } +} diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx new file mode 100644 index 0000000000000..8ff88af42b520 --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useState } from 'react'; +import ReactDOM from 'react-dom'; +import { css } from '@emotion/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageSection, + EuiPanel, + EuiSpacer, + EuiButton, + EuiTitle, +} from '@elastic/eui'; +import type { CoreStart } from '@kbn/core/public'; +import { LensConfigBuilder } from '@kbn/lens-embeddable-utils/config_builder/config_builder'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import type { StartDependencies } from './plugin'; +import { LensChart } from './embeddable'; +import { MultiPaneFlyout } from './flyout'; + +export const App = (props: { + core: CoreStart; + plugins: StartDependencies; + defaultDataView: DataView; + stateHelpers: Awaited>; +}) => { + const [container, setContainer] = useState(null); + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [isInlineEditingVisible, setIsinlineEditingVisible] = useState(false); + const [panelActive, setPanelActive] = useState(null); + + const configBuilder = useMemo( + () => new LensConfigBuilder(props.stateHelpers.formula, props.plugins.dataViews), + [props.plugins.dataViews, props.stateHelpers.formula] + ); + + return ( + + + + + + + + + + + + + + + +

#3: Embeddable inside a flyout

+
+ + +

+ In case you do not want to use a push flyout, you can check this example.{' '} +
+ In this example, we have a Lens embeddable inside a flyout and we want to + render the inline editing Component in a second slot of the same flyout. +

+
+ + + + { + setIsFlyoutVisible(true); + setPanelActive(3); + }} + > + Show flyout + + {isFlyoutVisible ? ( + { + setIsinlineEditingVisible(false); + if (container) { + ReactDOM.unmountComponentAtNode(container); + } + }} + onCancelCb={() => { + setIsinlineEditingVisible(false); + if (container) { + ReactDOM.unmountComponentAtNode(container); + } + }} + isESQL + isActive + /> + ), + }} + inlineEditingContent={{ + visible: isInlineEditingVisible, + }} + setContainer={setContainer} + onClose={() => { + setIsFlyoutVisible(false); + setIsinlineEditingVisible(false); + setPanelActive(null); + if (container) { + ReactDOM.unmountComponentAtNode(container); + } + }} + /> + ) : null} + + +
+
+
+
+
+
+
+ ); +}; diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx new file mode 100644 index 0000000000000..717a8b2d20f8e --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. 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, useRef, useEffect, useCallback, useMemo } from 'react'; +import type { + TypedLensByValueInput, + InlineEditLensEmbeddableContext, +} from '@kbn/lens-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { css } from '@emotion/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiPanel, + EuiButtonIcon, + EuiTitle, +} from '@elastic/eui'; +import { LensConfigBuilder } from '@kbn/lens-embeddable-utils/config_builder/config_builder'; +import type { StartDependencies } from './plugin'; +import { getConfigOptions } from './utils'; + +export const LensChart = (props: { + configBuilder: LensConfigBuilder; + plugins: StartDependencies; + defaultDataView: DataView; + isESQL?: boolean; + container?: HTMLElement | null; + isActive?: boolean; + setPanelActive?: (panelNum: number | null) => void; + setIsinlineEditingVisible?: (flag: boolean) => void; + onApplyCb?: () => void; + onCancelCb?: () => void; +}) => { + const ref = useRef(false); + const [embeddableInput, setEmbeddableInput] = useState( + undefined + ); + const [lensLoadEvent, setLensLoadEvent] = useState< + InlineEditLensEmbeddableContext['lensEvent'] | null + >(null); + + const { config, options } = useMemo(() => { + return getConfigOptions(props.defaultDataView, props.isESQL); + }, [props.defaultDataView, props.isESQL]); + + useEffect(() => { + ref.current = true; + props.configBuilder.build(config, options).then((input) => { + if (ref.current) { + setEmbeddableInput(input as TypedLensByValueInput); + } + }); + return () => { + ref.current = false; + }; + }, [config, props.configBuilder, options, props.plugins.dataViews]); + + const onLoad = useCallback( + ( + isLoading: boolean, + adapters: InlineEditLensEmbeddableContext['lensEvent']['adapters'] | undefined, + lensEmbeddableOutput$?: InlineEditLensEmbeddableContext['lensEvent']['embeddableOutput$'] + ) => { + const adapterTables = adapters?.tables?.tables; + if (adapterTables && !isLoading) { + setLensLoadEvent({ + adapters, + embeddableOutput$: lensEmbeddableOutput$, + }); + } + }, + [] + ); + + const triggerOptions: InlineEditLensEmbeddableContext | undefined = useMemo(() => { + if (lensLoadEvent && embeddableInput?.attributes) { + return { + attributes: embeddableInput?.attributes, + lensEvent: lensLoadEvent, + onUpdate: (newAttributes: TypedLensByValueInput['attributes']) => { + if (embeddableInput) { + const newInput = { + ...embeddableInput, + attributes: newAttributes, + }; + setEmbeddableInput(newInput); + } + }, + onApply: () => { + 'optional onApply callback!'; + props.onApplyCb?.(); + props.setPanelActive?.(null); + }, + onCancel: () => { + 'optional onCancel callback!'; + props.onCancelCb?.(); + props.setPanelActive?.(null); + }, + container: props.container, + }; + } + }, [embeddableInput, lensLoadEvent, props]); + const LensComponent = props.plugins.lens.EmbeddableComponent; + + return ( + + +

+ {props.isESQL + ? '#1: Inline editing of an ES|QL chart.' + : '#2: Inline editing of a dataview chart.'} +

+
+ + + + { + props?.setPanelActive?.(props.isESQL ? 1 : 2); + if (triggerOptions) { + props.plugins.uiActions + .getTrigger('IN_APP_EMBEDDABLE_EDIT_TRIGGER') + .exec(triggerOptions); + props?.setIsinlineEditingVisible?.(true); + } + }} + /> + + + {embeddableInput && ( + + )} + + +
+ ); +}; diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/flyout.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/flyout.tsx new file mode 100644 index 0000000000000..f916abbad18f2 --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/flyout.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. 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, useRef, useState } from 'react'; +import { css } from '@emotion/react'; +import { EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiPanel } from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; + +interface MainContent { + content: JSX.Element; +} + +interface InlineEditingContent { + visible?: boolean; +} + +interface Props { + mainContent: MainContent; + inlineEditingContent: InlineEditingContent; + setContainer?: (element: HTMLDivElement | null) => void; + onClose: () => void; +} + +export function MultiPaneFlyout({ + mainContent, + inlineEditingContent, + onClose, + setContainer, +}: Props) { + const [flexBasisCol1, setFlexBasisCol1] = useState('100%'); + const [flexBasisCol2, setFlexBasisCol2] = useState(!inlineEditingContent?.visible ? '0%' : '30%'); + + useEffect(() => { + setFlexBasisCol1(inlineEditingContent?.visible ? '70%' : '100%'); + setFlexBasisCol2(inlineEditingContent?.visible ? '30%' : '0%'); + }, [inlineEditingContent]); + + return ( + + + + + + + + {inlineEditingContent ? ( + + ) : null} + + + + ); +} + +function InlineEditingContent({ + visible, + setContainer, +}: { + visible?: boolean; + setContainer?: (element: HTMLDivElement | null) => void; +}) { + const containerRef = useRef(null); + const style = css` + padding: 0; + position: relative; + } +`; + + useEffect(() => { + if (containerRef?.current && setContainer) { + setContainer(containerRef.current); + } + }, [setContainer]); + return ( + + + + ); +} + +function MainContent({ content }: { content: JSX.Element }) { + const style = css` + padding-top: 12px; + padding-botton: 12px; + } + `; + + return ( + + {content} + + ); +} diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/image.png b/x-pack/examples/lens_embeddable_inline_editing_example/public/image.png new file mode 100644 index 0000000000000000000000000000000000000000..a7b47acc8cac51aaaa6002f6a5e98c27c5008118 GIT binary patch literal 155151 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 literal 0 HcmV?d00001 diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/index.ts b/x-pack/examples/lens_embeddable_inline_editing_example/public/index.ts new file mode 100644 index 0000000000000..4a3eb7100ffba --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LensInlineEditingPlugin } from './plugin'; + +export const plugin = () => new LensInlineEditingPlugin(); diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx new file mode 100644 index 0000000000000..411538e2df2ca --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx @@ -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 * as React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { EuiCallOut } from '@elastic/eui'; + +import type { CoreSetup, AppMountParameters } from '@kbn/core/public'; +import type { StartDependencies } from './plugin'; + +export const mount = + (coreSetup: CoreSetup) => + async ({ element }: AppMountParameters) => { + const [core, plugins] = await coreSetup.getStartServices(); + const { App } = await import('./app'); + + const dataView = await plugins.dataViews.getDefaultDataView(); + const stateHelpers = await plugins.lens.stateHelperApi(); + + const i18nCore = core.i18n; + + const reactElement = ( + + {dataView ? ( + + ) : ( + +

You need at least one dataview for this demo to work

+
+ )} +
+ ); + + render(reactElement, element); + return () => unmountComponentAtNode(element); + }; diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/plugin.ts b/x-pack/examples/lens_embeddable_inline_editing_example/public/plugin.ts new file mode 100644 index 0000000000000..5d970e2667ff7 --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/plugin.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { LensPublicStart } from '@kbn/lens-plugin/public'; +import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; +import { mount } from './mount'; +import image from './image.png'; + +export interface SetupDependencies { + developerExamples: DeveloperExamplesSetup; +} + +export interface StartDependencies { + dataViews: DataViewsPublicPluginStart; + lens: LensPublicStart; + uiActions: UiActionsStart; +} + +export class LensInlineEditingPlugin + implements Plugin +{ + public setup(core: CoreSetup, { developerExamples }: SetupDependencies) { + core.application.register({ + id: 'lens_embeddable_inline_editing_example', + title: 'Lens inline editing embeddable', + navLinkStatus: AppNavLinkStatus.hidden, + mount: mount(core), + }); + + developerExamples.register({ + appId: 'lens_embeddable_inline_editing_example', + title: 'Lens inline editing embeddable', + description: 'Inline editing of a Lens embeddable examples', + links: [ + { + label: 'README', + href: 'https://github.com/elastic/kibana/tree/main/x-pack/examples/lens_embeddable_inline_editing_example', + iconType: 'logoGithub', + size: 's', + target: '_blank', + }, + ], + image, + }); + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/utils.ts b/x-pack/examples/lens_embeddable_inline_editing_example/public/utils.ts new file mode 100644 index 0000000000000..f0069a543d368 --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/utils.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { + LensConfig, + LensConfigOptions, +} from '@kbn/lens-embeddable-utils/config_builder/types'; + +export const getConfigOptions = (dataView: DataView, isESQL?: boolean) => { + const index = dataView.getIndexPattern(); + const timeFieldName = dataView.getTimeField()?.name; + if (isESQL) { + return { + config: { + chartType: 'metric', + title: 'metric chart', + dataset: { + esql: `from ${index} | stats count=count()`, + }, + value: 'count', + } as LensConfig, + options: { + embeddable: true, + timeRange: { + from: 'now-30d', + to: 'now', + type: 'relative', + }, + query: { + esql: `from ${index} | stats count=count()`, + }, + } as unknown as LensConfigOptions, + }; + } else { + return { + config: { + chartType: 'heatmap', + title: 'heatmap chart', + dataset: { + index, + timeFieldName, + }, + xAxis: { + type: 'dateHistogram', + field: timeFieldName, + }, + value: 'count()', + } as LensConfig, + options: { + embeddable: true, + timeRange: { + from: 'now-30d', + to: 'now', + type: 'relative', + }, + } as LensConfigOptions, + }; + } +}; diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json b/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json new file mode 100644 index 0000000000000..e4727650106bd --- /dev/null +++ b/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../typings/**/*" + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/lens-plugin", + "@kbn/developer-examples-plugin", + "@kbn/data-views-plugin", + "@kbn/ui-actions-plugin", + "@kbn/kibana-react-plugin", + "@kbn/lens-embeddable-utils", + "@kbn/ui-theme", + ] +} diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index bb36de888b407..75a5c42c76444 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -118,7 +118,7 @@ describe('Lens Attribute', () => { ReportTypes.KPI ); - expect(lnsAttrKpi.getJSON().state.datasourceStates.formBased.layers.layer0.columns).toEqual({ + expect(lnsAttrKpi.getJSON().state.datasourceStates?.formBased?.layers.layer0.columns).toEqual({ 'x-axis-column-layer0': { dataType: 'date', isBucketed: true, diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx index acef8feb6da50..0944326cf43c2 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx @@ -117,6 +117,8 @@ export async function getEditLensConfiguration( isNewPanel, deletePanel, hidesSuggestions, + onApplyCb, + onCancelCb, }: EditLensConfigurationProps) => { if (!lensServices || !datasourceMap || !visualizationMap) { return ; @@ -212,6 +214,8 @@ export async function getEditLensConfiguration( setCurrentAttributes, isNewPanel, deletePanel, + onApplyCb, + onCancelCb, }; return getWrapper( diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx index e3df96840c883..1aae97977d714 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx @@ -65,6 +65,8 @@ export function LensEditConfigurationFlyout({ isNewPanel, deletePanel, hidesSuggestions, + onApplyCb, + onCancelCb, }: EditConfigPanelProps) { const euiTheme = useEuiTheme(); const previousAttributes = useRef(attributes); @@ -173,6 +175,7 @@ export function LensEditConfigurationFlyout({ if (isNewPanel && deletePanel) { deletePanel(); } + onCancelCb?.(); closeFlyout?.(); }, [ attributesChanged, @@ -186,6 +189,7 @@ export function LensEditConfigurationFlyout({ updatePanelState, updateSuggestion, updateByRefInput, + onCancelCb, ]); const onApply = useCallback(() => { @@ -220,10 +224,12 @@ export function LensEditConfigurationFlyout({ saveByRef?.(attrs); updateByRefInput?.(savedObjectId); } + onApplyCb?.(); closeFlyout?.(); }, [ savedObjectId, closeFlyout, + onApplyCb, datasourceStates, visualization.state, activeVisualization, diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts index 6b5a2bb501275..bb6b1157f43b9 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts @@ -82,6 +82,10 @@ export interface EditConfigPanelProps { deletePanel?: () => void; /** If set to true the layout changes to accordion and the text based query (i.e. ES|QL) can be edited */ hidesSuggestions?: boolean; + /** Optional callback for apply flyout button */ + onApplyCb?: () => void; + /** Optional callback for cancel flyout button */ + onCancelCb?: () => void; } export interface LayerConfigurationProps { diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts index 85724c871cda6..28becae5e6071 100644 --- a/x-pack/plugins/lens/public/async_services.ts +++ b/x-pack/plugins/lens/public/async_services.ts @@ -52,3 +52,4 @@ export * from './chart_info_api'; export * from './trigger_actions/open_in_discover_helpers'; export * from './trigger_actions/open_lens_config/edit_action_helpers'; export * from './trigger_actions/open_lens_config/create_action_helpers'; +export * from './trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers'; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx index 986f2f65c693e..bdbcfb49617bb 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx @@ -46,7 +46,7 @@ type LensAttributes = Omit< visualizationType: TVisType; state: Omit & { datasourceStates: { - formBased: FormBasedPersistedState; + formBased?: FormBasedPersistedState; textBased?: TextBasedPersistedState; }; visualization: TVisState; diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index c611952f44f0a..a9f8e0eba3dc4 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -106,6 +106,8 @@ export type { ReferenceLineLayerConfig, } from '@kbn/expression-xy-plugin/common'; +export type { InlineEditLensEmbeddableContext } from './trigger_actions/open_lens_config/in_app_embeddable_edit/types'; + export type { LensEmbeddableInput, LensSavedObjectAttributes, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 872e1a566ab20..61ffb1d0686d0 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -107,6 +107,11 @@ import { getLensAliasConfig } from './vis_type_alias'; import { createOpenInDiscoverAction } from './trigger_actions/open_in_discover_action'; import { ConfigureInLensPanelAction } from './trigger_actions/open_lens_config/edit_action'; import { CreateESQLPanelAction } from './trigger_actions/open_lens_config/create_action'; +import { + inAppEmbeddableEditTrigger, + IN_APP_EMBEDDABLE_EDIT_TRIGGER, +} from './trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_trigger'; +import { EditLensEmbeddableAction } from './trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; import { visualizeTSVBAction } from './trigger_actions/visualize_tsvb_actions'; import { visualizeAggBasedVisAction } from './trigger_actions/visualize_agg_based_vis_actions'; @@ -575,6 +580,10 @@ export class LensPlugin { if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); } + + // this trigger enables external consumers to use the inline editing flyout + startDependencies.uiActions.registerTrigger(inAppEmbeddableEditTrigger); + startDependencies.uiActions.addTriggerAction( VISUALIZE_FIELD_TRIGGER, visualizeFieldAction(core.application) @@ -600,8 +609,17 @@ export class LensPlugin { core.overlays, core.theme ); + // dashboard edit panel action startDependencies.uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', editInLensAction); + // Allows the Lens embeddable to easily open the inapp editing flyout + const editLensEmbeddableAction = new EditLensEmbeddableAction(startDependencies, core); + // embeddable edit panel action + startDependencies.uiActions.addTriggerAction( + IN_APP_EMBEDDABLE_EDIT_TRIGGER, + editLensEmbeddableAction + ); + // Displays the add ESQL panel in the dashboard add Panel menu const createESQLPanelAction = new CreateESQLPanelAction(startDependencies, core); startDependencies.uiActions.addTriggerAction('ADD_PANEL_TRIGGER', createESQLPanelAction); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx new file mode 100644 index 0000000000000..a6c7a8bcf6bc0 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { CoreStart } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import { createMockStartDependencies } from '../../../editor_frame_service/mocks'; +import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { EditLensEmbeddableAction } from './in_app_embeddable_edit_action'; + +describe('inapp editing of Lens embeddable', () => { + const core = coreMock.createStart(); + const mockStartDependencies = + createMockStartDependencies() as unknown as LensPluginStartDependencies; + describe('compatibility check', () => { + const attributes = { + title: 'An extremely cool default document!', + expression: 'definitely a valid expression', + visualizationType: 'testVis', + state: { + query: { esql: 'from test' }, + filters: [{ query: { match_phrase: { src: 'test' } }, meta: { index: 'index-pattern-0' } }], + datasourceStates: { + testDatasource: 'datasource', + }, + visualization: {}, + }, + references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], + } as unknown as TypedLensByValueInput['attributes']; + it('is incompatible for ESQL charts and if ui setting for ES|QL is off', async () => { + const inAppEditAction = new EditLensEmbeddableAction(mockStartDependencies, core); + const context = { + attributes, + lensEvent: { + adapters: {}, + embeddableOutput$: undefined, + }, + onUpdate: jest.fn(), + }; + const isCompatible = await inAppEditAction.isCompatible(context); + + expect(isCompatible).toBeFalsy(); + }); + + it('is compatible for ESQL charts and if ui setting for ES|QL is on', async () => { + const updatedCore = { + ...core, + uiSettings: { + ...core.uiSettings, + get: (setting: string) => { + return setting === 'discover:enableESQL'; + }, + }, + } as CoreStart; + const inAppEditAction = new EditLensEmbeddableAction(mockStartDependencies, updatedCore); + const context = { + attributes, + lensEvent: { + adapters: {}, + embeddableOutput$: undefined, + }, + onUpdate: jest.fn(), + }; + const isCompatible = await inAppEditAction.isCompatible(context); + + expect(isCompatible).toBeTruthy(); + }); + + it('is compatible for dataview charts', async () => { + const inAppEditAction = new EditLensEmbeddableAction(mockStartDependencies, core); + const newAttributes = { + ...attributes, + state: { + ...attributes.state, + query: { + language: 'kuery', + query: '', + }, + }, + }; + const context = { + attributes: newAttributes, + lensEvent: { + adapters: {}, + embeddableOutput$: undefined, + }, + onUpdate: jest.fn(), + }; + const isCompatible = await inAppEditAction.isCompatible(context); + + expect(isCompatible).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx new file mode 100644 index 0000000000000..c132b5e88c6c4 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type { CoreStart } from '@kbn/core/public'; +import { Action } from '@kbn/ui-actions-plugin/public'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import type { InlineEditLensEmbeddableContext } from './types'; + +const ACTION_EDIT_LENS_EMBEDDABLE = 'ACTION_EDIT_LENS_EMBEDDABLE'; + +export const getAsyncHelpers = async () => await import('../../../async_services'); + +export class EditLensEmbeddableAction implements Action { + public type = ACTION_EDIT_LENS_EMBEDDABLE; + public id = ACTION_EDIT_LENS_EMBEDDABLE; + public order = 50; + + constructor( + protected readonly startDependencies: LensPluginStartDependencies, + protected readonly core: CoreStart + ) {} + + public getDisplayName(): string { + return i18n.translate('xpack.lens.app.editLensEmbeddableLabel', { + defaultMessage: 'Edit visualization', + }); + } + + public getIconType() { + return 'pencil'; + } + + public async isCompatible({ attributes }: InlineEditLensEmbeddableContext) { + const { isEmbeddableEditActionCompatible } = await getAsyncHelpers(); + return isEmbeddableEditActionCompatible(this.core, attributes); + } + + public async execute({ + attributes, + lensEvent, + container, + onUpdate, + onApply, + onCancel, + }: InlineEditLensEmbeddableContext) { + const { executeEditEmbeddableAction } = await getAsyncHelpers(); + if (attributes) { + executeEditEmbeddableAction({ + deps: this.startDependencies, + core: this.core, + attributes, + lensEvent, + container, + onUpdate, + onApply, + onCancel, + }); + } + } +} diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx new file mode 100644 index 0000000000000..584aa7aaf132a --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. 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 ReactDOM from 'react-dom'; +import type { CoreStart } from '@kbn/core/public'; +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import type { LensPluginStartDependencies } from '../../../plugin'; +import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { extractReferencesFromState } from '../../../utils'; +import type { LensChartLoadEvent } from './types'; + +export function isEmbeddableEditActionCompatible( + core: CoreStart, + attributes: TypedLensByValueInput['attributes'] +) { + // for ES|QL is compatible only when advanced setting is enabled + const query = attributes.state.query; + return isOfAggregateQueryType(query) ? core.uiSettings.get('discover:enableESQL') : true; +} + +export async function executeEditEmbeddableAction({ + deps, + core, + attributes, + lensEvent, + container, + onUpdate, + onApply, + onCancel, +}: { + deps: LensPluginStartDependencies; + core: CoreStart; + attributes: TypedLensByValueInput['attributes']; + lensEvent: LensChartLoadEvent; + container?: HTMLElement | null; + onUpdate: (newAttributes: TypedLensByValueInput['attributes']) => void; + onApply?: () => void; + onCancel?: () => void; +}) { + const isCompatibleAction = isEmbeddableEditActionCompatible(core, attributes); + if (!isCompatibleAction) { + throw new IncompatibleActionError(); + } + + const { getEditLensConfiguration, getVisualizationMap, getDatasourceMap } = await import( + '../../../async_services' + ); + const visualizationMap = getVisualizationMap(); + const datasourceMap = getDatasourceMap(); + const query = attributes.state.query; + const activeDatasourceId = isOfAggregateQueryType(query) ? 'textBased' : 'formBased'; + + const onUpdatePanelState = ( + datasourceState: unknown, + visualizationState: unknown, + visualizationType?: string + ) => { + if (attributes.state) { + const datasourceStates = { + ...attributes.state.datasourceStates, + [activeDatasourceId]: datasourceState, + }; + + const references = extractReferencesFromState({ + activeDatasources: Object.keys(datasourceStates).reduce( + (acc, datasourceId) => ({ + ...acc, + [datasourceId]: datasourceMap[datasourceId], + }), + {} + ), + datasourceStates: Object.fromEntries( + Object.entries(datasourceStates).map(([id, state]) => [id, { isLoading: false, state }]) + ), + visualizationState, + activeVisualization: visualizationType ? visualizationMap[visualizationType] : undefined, + }); + + const attrs = { + ...attributes, + state: { + ...attributes.state, + visualization: visualizationState, + datasourceStates, + }, + references, + visualizationType: visualizationType ?? attributes.visualizationType, + } as TypedLensByValueInput['attributes']; + + onUpdate(attrs); + } + }; + + const onUpdateSuggestion = (attrs: TypedLensByValueInput['attributes']) => { + const newAttributes = { + ...attributes, + ...attrs, + }; + onUpdate(newAttributes); + }; + + const Component = await getEditLensConfiguration(core, deps, visualizationMap, datasourceMap); + const ConfigPanel = ( + + ); + + // in case an element is given render the component in the container, + // otherwise a flyout will open + if (container) { + ReactDOM.render(ConfigPanel, container); + } else { + const handle = core.overlays.openFlyout( + toMountPoint( + React.cloneElement(ConfigPanel, { + closeFlyout: () => { + handle.close(); + }, + }), + { + theme$: core.theme.theme$, + } + ), + { + className: 'lnsConfigPanel__overlay', + size: 's', + 'data-test-subj': 'customizeLens', + type: 'push', + paddingSize: 'm', + hideCloseButton: true, + onClose: (overlayRef) => { + overlayRef.close(); + }, + outsideClickCloses: true, + } + ); + } +} diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_trigger.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_trigger.ts new file mode 100644 index 0000000000000..41bd24e6a62ea --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_trigger.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type { Trigger } from '@kbn/ui-actions-plugin/public'; + +export const IN_APP_EMBEDDABLE_EDIT_TRIGGER = 'IN_APP_EMBEDDABLE_EDIT_TRIGGER'; +export const inAppEmbeddableEditTrigger: Trigger = { + id: IN_APP_EMBEDDABLE_EDIT_TRIGGER, + title: i18n.translate('xpack.lens.inAppEditTrigger.title', { + defaultMessage: 'In-app embeddable edit', + }), + description: i18n.translate('xpack.lens.inAppEditTrigger.description', { + defaultMessage: 'Triggers an in app flyout on the current embeddable', + }), +}; diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts new file mode 100644 index 0000000000000..e1eadf7a32660 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; +import type { EmbeddableOutput } from '@kbn/embeddable-plugin/public'; +import type { Observable } from 'rxjs'; +import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; + +export interface LensChartLoadEvent { + /** + * Inspector adapters for the request + */ + adapters: Partial; + /** + * Observable of the lens embeddable output + */ + embeddableOutput$?: Observable; +} + +export interface InlineEditLensEmbeddableContext { + // attributes of the Lens embeddable + attributes: TypedLensByValueInput['attributes']; + // chart event, can be fetched from the onLoad embeddable callback + lensEvent: LensChartLoadEvent; + // callback which runs every time something changes in the dimension panel + onUpdate: (newAttributes: TypedLensByValueInput['attributes']) => void; + // optional onApply callback + onApply?: () => void; + // optional onCancel callback + onCancel?: () => void; + // custom container element, use in case you need to render outside a flyout + // in that case, the styling is responsibility of the consumer + container?: HTMLElement | null; +} diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index 764ff5f2df04f..fc2fd5df4c85a 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -271,3 +271,11 @@ Lens has a lot of UI elements – to make it easier to refer to them in issues o * **Suggestion panel** Panel to the bottom showing previews for suggestions on how to change the current chart ![Layout](./layout.png "Layout") + + +# Inline Editing of a Lens Embeddable + +If you have a Lens embeddable in your application and you want to allow inline editing you can do it with 3 ways: +- If you use a portable dashboard, the functionality is built in and you don't need to do anything +- If you don't have a portable dashboard then you can use UI actions to retrieve the inline editing component. For more information check out the example in `x-pack/examples/lens_embeddable_inline_editing_example`. +- The component is also exported from Lens start contract. Check the `EditLensConfigPanelApi`. This is advised to be used only when the 2 above cases can't be used. \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx index 1946c12aacdcf..b0ee36eff0747 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx @@ -102,7 +102,9 @@ describe('RiskSummary', () => { const lensAttributes: LensAttributes = mockVisualizationEmbeddable.mock.calls[0][0].lensAttributes; - const datasourceLayers = Object.values(lensAttributes.state.datasourceStates.formBased.layers); + const datasourceLayers = Object.values( + lensAttributes.state.datasourceStates.formBased?.layers ?? {} + ); const firstColumn = Object.values(datasourceLayers[0].columns)[0]; expect(lensAttributes.state.query.query).toEqual('host.name: test'); @@ -126,7 +128,9 @@ describe('RiskSummary', () => { const lensAttributes: LensAttributes = mockVisualizationEmbeddable.mock.calls[0][0].lensAttributes; - const datasourceLayers = Object.values(lensAttributes.state.datasourceStates.formBased.layers); + const datasourceLayers = Object.values( + lensAttributes.state.datasourceStates.formBased?.layers ?? {} + ); const firstColumn = Object.values(datasourceLayers[0].columns)[0]; expect(lensAttributes.state.query.query).toEqual('user.name: test'); diff --git a/yarn.lock b/yarn.lock index 28057e7c10d95..9a5867e2361bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4986,6 +4986,9 @@ version "0.0.0" uid "" +"@kbn/lens-inline-editing-example-plugin@link:x-pack/examples/lens_embeddable_inline_editing_example": + + "@kbn/lens-plugin@link:x-pack/plugins/lens": version "0.0.0" uid "" From 35cccc29631e199979e6887eaf469444b285f637 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Fri, 5 Jan 2024 14:26:30 +0100 Subject: [PATCH 07/10] [Security Solution] OpenAPI linter (#171851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Resolves: https://github.com/elastic/security-team/issues/8099** ## Summary This PR adds an a command to lint OpenAPI specs defined in Security Solution plugin. ## Details We have a number of OpenAPI specs defined in Security Solution plugin. While `@kbn/openapi-generator` package processes the specs and makes sure the specs are parsable and processable we don't have proper specs linter set up. This PR introduces OpenAPI specs linting by leveraging [Redocly CLI](https://github.com/Redocly/redocly-cli)'s `lint` command with a custom configuration placed in `@kbn/openapi-generator` package . Configuration includes reasonable best practices by using [built-in Redocly rules](https://redocly.com/docs/cli/rules/built-in-rules/). The lint utility fulfil the following requirements - Validates yaml files against OpenAPI specification. It supports `3.0` and `3.1`. - Validates `x-modify` property to have only `partial`, `required` or `requiredOptional` values. - Checks for reasonable best practices and displays a warning message when it's not met. Reasonable best practices are based on the [recommended ruleset](https://redocly.com/docs/cli/rules/recommended/#recommended-ruleset). The lint utility has been incorporated into the existing OpenAPI generator, and linting is performed before generation. ### Tool selection [Swagger CLI](https://github.com/APIDevTools/swagger-cli) is a well known tool to validate OpenAPI specs. On November 15th 2023 the repo has been archived with a message in README > ⚠️ Swagger CLI has been deprecated, due to the maintenance burnden of trying to keep up with expectations of a huge userbase with little to no pull requests or support. [Redocly CLI](https://redocly.com/redocly-cli/) covers all of the same functionality, and has more advanced linting with custom rules, and we highly recommend using that instead. They have conveniently provided a [migration guide](https://redocly.com/docs/cli/guides/migrate-from-swagger-cli/) for existing Swagger CLI users. Read the review of [Redocly CLI from APIs You Won't Hate](https://apisyouwonthate.com/blog/redocly-cli/). Taking it into account choice falls on **Redocly CLI**. ## How to test? Change directory to Security Solution plugin's root and run linting by using the following commands from Kibana root ```sh cd x-pack/plugins/security_solution yarn openapi:generate ``` --------- Co-authored-by: Georgii Gorbachev --- package.json | 1 + packages/kbn-openapi-generator/index.ts | 1 + .../redocly_linter/config.yaml | 27 + .../extra_linter_rules_plugin.js | 49 ++ packages/kbn-openapi-generator/src/cli.ts | 5 + .../src/openapi_generator.ts | 11 +- .../src/openapi_linter.ts | 47 ++ src/dev/license_checker/config.ts | 2 +- src/dev/yarn_deduplicate/index.ts | 2 +- .../osquery/scripts/openapi/generate.js | 2 + .../create_asset_criticality.schema.yaml | 3 +- .../delete_asset_criticality.schema.yaml | 3 +- .../get_asset_criticality.schema.yaml | 3 +- ...t_asset_criticality_privileges.schema.yaml | 3 +- .../get_asset_criticality_status.schema.yaml | 1 + x-pack/plugins/security_solution/package.json | 2 +- yarn.lock | 515 ++++++++++++++++-- 17 files changed, 620 insertions(+), 57 deletions(-) create mode 100644 packages/kbn-openapi-generator/redocly_linter/config.yaml create mode 100644 packages/kbn-openapi-generator/redocly_linter/extra_linter_rules_plugin.js create mode 100644 packages/kbn-openapi-generator/src/openapi_linter.ts diff --git a/package.json b/package.json index 0a4d9ebfcebdb..23e49b0d4cb35 100644 --- a/package.json +++ b/package.json @@ -1307,6 +1307,7 @@ "@octokit/rest": "^16.35.0", "@openpgp/web-stream-tools": "^0.0.10", "@parcel/watcher": "^2.1.0", + "@redocly/cli": "^1.6.0", "@storybook/addon-a11y": "^6.5.16", "@storybook/addon-actions": "^6.5.16", "@storybook/addon-controls": "^6.5.16", diff --git a/packages/kbn-openapi-generator/index.ts b/packages/kbn-openapi-generator/index.ts index eeaad5343dc9f..711ee4b6269d1 100644 --- a/packages/kbn-openapi-generator/index.ts +++ b/packages/kbn-openapi-generator/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ +export * from './src/openapi_linter'; export * from './src/openapi_generator'; export * from './src/cli'; diff --git a/packages/kbn-openapi-generator/redocly_linter/config.yaml b/packages/kbn-openapi-generator/redocly_linter/config.yaml new file mode 100644 index 0000000000000..b423d9172b1c8 --- /dev/null +++ b/packages/kbn-openapi-generator/redocly_linter/config.yaml @@ -0,0 +1,27 @@ +# Recommended Redocly CLI ruleset https://redocly.com/docs/cli/rules/recommended/#recommended-ruleset +# Redocly CLI custom plugins https://redocly.com/docs/cli/custom-plugins/ +plugins: + - extra_linter_rules_plugin.js + +rules: + spec: error + spec-strict-refs: warn + no-path-trailing-slash: error + no-identical-paths: error + no-ambiguous-paths: warn + no-unresolved-refs: error + no-enum-type-mismatch: error + component-name-unique: error + path-declaration-must-exist: error + path-not-include-query: error + path-parameters-defined: warn + operation-description: warn + operation-2xx-response: error + operation-4xx-response: warn + operation-operationId: error + operation-operationId-unique: error + operation-summary: warn + operation-operationId-url-safe: error + operation-parameters-unique: error + boolean-parameter-prefixes: warn + extra-linter-rules-plugin/valid-x-modify: error diff --git a/packages/kbn-openapi-generator/redocly_linter/extra_linter_rules_plugin.js b/packages/kbn-openapi-generator/redocly_linter/extra_linter_rules_plugin.js new file mode 100644 index 0000000000000..caef408c366b2 --- /dev/null +++ b/packages/kbn-openapi-generator/redocly_linter/extra_linter_rules_plugin.js @@ -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 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. + */ + +const KNOWN_X_MODIFY_VALUES = ['partial', 'required', 'requiredOptional']; + +function ValidXModify() { + return { + any: { + leave(node, ctx) { + if (typeof node !== 'object' || !('x-modify' in node)) { + return; + } + + if (!KNOWN_X_MODIFY_VALUES.includes(node['x-modify'])) + ctx.report({ + message: `Only ${KNOWN_X_MODIFY_VALUES.join(', ')} can be used for x-modify`, + location: ctx.location.child('x-modify'), + }); + }, + }, + ref: { + leave(node, ctx) { + if (typeof node !== 'object' || !('x-modify' in node)) { + return; + } + + if (!KNOWN_X_MODIFY_VALUES.includes(node['x-modify'])) + ctx.report({ + message: `Only ${KNOWN_X_MODIFY_VALUES.join(', ')} can be used for x-modify`, + location: ctx.location.child('x-modify'), + }); + }, + }, + }; +} + +module.exports = { + id: 'extra-linter-rules-plugin', + rules: { + oas3: { + 'valid-x-modify': ValidXModify, + }, + }, +}; diff --git a/packages/kbn-openapi-generator/src/cli.ts b/packages/kbn-openapi-generator/src/cli.ts index 9eb91dd9bba94..6361c0a20de3b 100644 --- a/packages/kbn-openapi-generator/src/cli.ts +++ b/packages/kbn-openapi-generator/src/cli.ts @@ -31,6 +31,11 @@ export function runCli() { default: 'zod_operation_schema' as const, choices: AVAILABLE_TEMPLATES, }) + .option('skipLinting', { + describe: 'Whether linting should be skipped', + type: 'boolean', + default: false, + }) .showHelpOnFail(false), (argv) => { generate(argv).catch((err) => { diff --git a/packages/kbn-openapi-generator/src/openapi_generator.ts b/packages/kbn-openapi-generator/src/openapi_generator.ts index 539994a258126..60efd762dade7 100644 --- a/packages/kbn-openapi-generator/src/openapi_generator.ts +++ b/packages/kbn-openapi-generator/src/openapi_generator.ts @@ -17,6 +17,7 @@ import { fixEslint } from './lib/fix_eslint'; import { formatOutput } from './lib/format_output'; import { getGeneratedFilePath } from './lib/get_generated_file_path'; import { removeGenArtifacts } from './lib/remove_gen_artifacts'; +import { lint } from './openapi_linter'; import { getGenerationContext } from './parser/get_generation_context'; import type { OpenApiDocument } from './parser/openapi_types'; import { initTemplateService, TemplateName } from './template_service/template_service'; @@ -25,10 +26,18 @@ export interface GeneratorConfig { rootDir: string; sourceGlob: string; templateName: TemplateName; + skipLinting?: boolean; } export const generate = async (config: GeneratorConfig) => { - const { rootDir, sourceGlob, templateName } = config; + const { rootDir, sourceGlob, templateName, skipLinting } = config; + + if (!skipLinting) { + await lint({ + rootDir, + sourceGlob, + }); + } console.log(chalk.bold(`Generating API route schemas`)); console.log(chalk.bold(`Working directory: ${chalk.underline(rootDir)}`)); diff --git a/packages/kbn-openapi-generator/src/openapi_linter.ts b/packages/kbn-openapi-generator/src/openapi_linter.ts new file mode 100644 index 0000000000000..afd31a77bdee1 --- /dev/null +++ b/packages/kbn-openapi-generator/src/openapi_linter.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 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. + */ + +/* eslint-disable no-console */ + +import { resolve } from 'path'; +import globby from 'globby'; +import execa from 'execa'; +import chalk from 'chalk'; +import { REPO_ROOT } from '@kbn/repo-info'; + +export interface LinterConfig { + rootDir: string; + sourceGlob: string; +} + +export const lint = async (config: LinterConfig) => { + const { rootDir, sourceGlob } = config; + + const sourceFilesGlob = resolve(rootDir, sourceGlob); + const schemaPaths = await globby([sourceFilesGlob]); + + console.log(chalk.bold(`Linting API route schemas`)); + + try { + await execa( + './node_modules/.bin/redocly', + [ + 'lint', + '--config=packages/kbn-openapi-generator/redocly_linter/config.yaml', + ...schemaPaths, + ], + { + cwd: REPO_ROOT, + stderr: process.stderr, + stdout: process.stdout, + } + ); + } catch { + throw new Error('Linter failed'); + } +}; diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 6ba5deb6408ac..db69bab07e1ea 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -73,7 +73,7 @@ export const LICENSE_ALLOWED = [ // The following list only applies to licenses that // we wanna allow in packages only used as dev dependencies -export const DEV_ONLY_LICENSE_ALLOWED = ['MPL-2.0']; +export const DEV_ONLY_LICENSE_ALLOWED = ['MPL-2.0', '(MPL-2.0 OR Apache-2.0)']; // there are some licenses which should not be globally allowed // but can be brought in on a per-package basis diff --git a/src/dev/yarn_deduplicate/index.ts b/src/dev/yarn_deduplicate/index.ts index 13c12ac5e91b7..1b1bade3b8b2c 100644 --- a/src/dev/yarn_deduplicate/index.ts +++ b/src/dev/yarn_deduplicate/index.ts @@ -16,7 +16,7 @@ const yarnLock = readFileSync(yarnLockFile, 'utf-8'); const output = fixDuplicates(yarnLock, { useMostCommon: false, excludeScopes: ['@types'], - excludePackages: ['axe-core', '@babel/types'], + excludePackages: ['axe-core', '@babel/types', 'csstype'], }); writeFileSync(yarnLockFile, output); diff --git a/x-pack/plugins/osquery/scripts/openapi/generate.js b/x-pack/plugins/osquery/scripts/openapi/generate.js index 018a965702c3e..35c099301e81c 100644 --- a/x-pack/plugins/osquery/scripts/openapi/generate.js +++ b/x-pack/plugins/osquery/scripts/openapi/generate.js @@ -17,4 +17,6 @@ generate({ rootDir: OSQUERY_ROOT, sourceGlob: './**/*.schema.yaml', templateName: 'zod_operation_schema', + // TODO: Fix lint errors + skipLinting: true, }); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml index bb5e682155064..cc8c980809f9b 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml @@ -12,6 +12,7 @@ servers: paths: /internal/asset_criticality: post: + operationId: AssetCriticalityCreateRecord summary: Create Criticality Record requestBody: required: true @@ -27,4 +28,4 @@ paths: schema: $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' '400': - description: Invalid request \ No newline at end of file + description: Invalid request diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml index fbdb4feb19e52..cada6a62fcaac 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml @@ -12,6 +12,7 @@ servers: paths: /internal/asset_criticality: delete: + operationId: AssetCriticalityDeleteRecord summary: Delete Criticality Record parameters: - $ref: './common.schema.yaml#/components/parameters/id_value' @@ -20,4 +21,4 @@ paths: '200': description: Successful response '400': - description: Invalid request \ No newline at end of file + description: Invalid request diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml index 1411f2a08734f..777666daccb2f 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml @@ -12,6 +12,7 @@ servers: paths: /internal/asset_criticality: get: + operationId: AssetCriticalityGetRecord summary: Get Criticality Record parameters: - $ref: './common.schema.yaml#/components/parameters/id_value' @@ -26,4 +27,4 @@ paths: '400': description: Invalid request '404': - description: Criticality record not found \ No newline at end of file + description: Criticality record not found diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml index b877b90efca94..6f1734262c667 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml @@ -12,6 +12,7 @@ servers: paths: /internal/asset_criticality/privileges: get: + operationId: AssetCriticalityGetPrivileges summary: Get Asset Criticality Privileges responses: '200': @@ -26,4 +27,4 @@ paths: ".asset-criticality.asset-criticality-*": read: true write: false - has_all_required: false \ No newline at end of file + has_all_required: false diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml index a5a6b50354688..a62450cbcff4d 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_status.schema.yaml @@ -12,6 +12,7 @@ servers: paths: /internal/asset_criticality/status: get: + operationId: AssetCriticalityGetStatus summary: Get Asset Criticality Status responses: '200': diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 5f0613c3e89b8..3d9effa5983bf 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -29,4 +29,4 @@ "openapi:generate:debug": "node --inspect-brk scripts/openapi/generate", "openapi:bundle": "node scripts/openapi/bundle" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 9a5867e2361bc..21b560fc24e86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2010,6 +2010,13 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== +"@emotion/is-prop-valid@1.2.1", "@emotion/is-prop-valid@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" + integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/is-prop-valid@^0.8.8": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -2017,13 +2024,6 @@ dependencies: "@emotion/memoize" "0.7.4" -"@emotion/is-prop-valid@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== - dependencies: - "@emotion/memoize" "^0.8.1" - "@emotion/jest@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/jest/-/jest-11.11.0.tgz#4d64b33052308739dcdd7396fd2bc902f7244f82" @@ -2123,6 +2123,11 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@emotion/unitless@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" + integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== + "@emotion/unitless@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" @@ -2295,6 +2300,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6" integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== +"@exodus/schemasafe@^1.0.0-rc.2": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.3.0.tgz#731656abe21e8e769a7f70a4d833e6312fe59b7f" + integrity sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw== + "@fastify/busboy@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" @@ -7475,6 +7485,54 @@ unbzip2-stream "1.4.3" yargs "17.7.2" +"@redocly/ajv@^8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.11.0.tgz#2fad322888dc0113af026e08fceb3e71aae495ae" + integrity sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +"@redocly/cli@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.6.0.tgz#d3f6c8d6822eead487c2cb814d131e17d05c961f" + integrity sha512-0naVFJGR2tVcpMIHSFRr2HAoyy70qMqDAP6kXcnOdkGkwLRJ8s/5n1STwsym/yZwNkhrt2M0cKT6KAMlTUeCeg== + dependencies: + "@redocly/openapi-core" "1.6.0" + chokidar "^3.5.1" + colorette "^1.2.0" + core-js "^3.32.1" + get-port-please "^3.0.1" + glob "^7.1.6" + handlebars "^4.7.6" + mobx "^6.0.4" + node-fetch "^2.6.1" + react "^17.0.0 || ^18.2.0" + react-dom "^17.0.0 || ^18.2.0" + redoc "~2.1.3" + semver "^7.5.2" + simple-websocket "^9.0.0" + styled-components "^6.0.7" + yargs "17.0.1" + +"@redocly/openapi-core@1.6.0", "@redocly/openapi-core@^1.0.0-rc.2": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.6.0.tgz#09aee5e21a9cbad08f3230ced16685d043a9b197" + integrity sha512-oao6Aey4peLKfagzWGb6N7OBI6CoDWEP4ka/XjrUNZw+UoKVVg3hVBXW4Vr3CJ2O8j6wEa2i+Lbb92VQQsoxwg== + dependencies: + "@redocly/ajv" "^8.11.0" + "@types/node" "^14.11.8" + colorette "^1.2.0" + js-levenshtein "^1.1.6" + js-yaml "^4.1.0" + lodash.isequal "^4.5.0" + minimatch "^5.0.1" + node-fetch "^2.6.1" + pluralize "^8.0.0" + yaml-ast-parser "0.0.43" + "@redux-saga/core@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4" @@ -9564,6 +9622,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.7": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/json-stable-stringify@^1.0.32": version "1.0.32" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" @@ -9797,7 +9860,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@20.10.5", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=18.0.0", "@types/node@^10.1.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^18.11.18", "@types/node@^18.17.5": +"@types/node@*", "@types/node@20.10.5", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=18.0.0", "@types/node@^10.1.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.11.8", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^18.11.18", "@types/node@^18.17.5": version "20.10.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== @@ -10234,6 +10297,11 @@ "@types/react-native" "*" csstype "^2.2.0" +"@types/stylis@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.0.tgz#199a3f473f0c3a6f6e4e1b17cdbc967f274bdc6b" + integrity sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw== + "@types/superagent@*": version "3.8.4" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.4.tgz#24a5973c7d1a9c024b4bbda742a79267c33fb86a" @@ -13039,7 +13107,7 @@ cheerio@^1.0.0-rc.12, cheerio@^1.0.0-rc.3: parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" -chokidar@3.5.3, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.3: +chokidar@3.5.3, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -13141,10 +13209,10 @@ classnames@2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -classnames@^2.2.6, classnames@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== +classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== clean-css@^4.2.3: version "4.2.3" @@ -13320,10 +13388,10 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -clsx@^1.0.4, clsx@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" - integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^1.0.4, clsx@^1.1.0, clsx@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== co@^4.6.0: version "4.6.0" @@ -13418,10 +13486,10 @@ colord@^2.9.1, colord@^2.9.2: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== -colorette@^1.2.1, colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^1.2.0, colorette@^1.2.1, colorette@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== colorette@^2.0.10, colorette@^2.0.14: version "2.0.19" @@ -13764,10 +13832,10 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.9: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.4, core-js@^3.34.0, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: - version "3.34.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.34.0.tgz#5705e6ad5982678612e96987d05b27c6c7c274a5" - integrity sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag== +core-js@^3.0.4, core-js@^3.32.1, core-js@^3.34.0, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: + version "3.35.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.35.0.tgz#58e651688484f83c34196ca13f099574ee53d6b4" + integrity sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg== core-util-is@1.0.2, core-util-is@^1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -14025,10 +14093,10 @@ css-select@^5.1.0: domutils "^3.0.1" nth-check "^2.0.1" -css-to-react-native@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" - integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== +css-to-react-native@3.2.0, css-to-react-native@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== dependencies: camelize "^1.0.0" css-color-keywords "^1.0.0" @@ -14145,6 +14213,11 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +csstype@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + csstype@^2.2.0, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" @@ -14685,6 +14758,11 @@ decimal.js@^10.4.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== +decko@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decko/-/decko-1.2.0.tgz#fd43c735e967b8013306884a56fbe665996b6817" + integrity sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ== + decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -15299,6 +15377,11 @@ domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +dompurify@^2.2.8: + version "2.4.7" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.7.tgz#277adeb40a2c84be2d42a8bcd45f582bfa4d0cfc" + integrity sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ== + domutils@^2.0.0, domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -15931,6 +16014,11 @@ es6-map@^0.1.5: es6-symbol "~3.1.1" event-emitter "~0.3.5" +es6-promise@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" + integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg== + es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -16445,7 +16533,7 @@ eventemitter2@6.4.7: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== -eventemitter3@^4.0.0, eventemitter3@^4.0.4: +eventemitter3@^4.0.0, eventemitter3@^4.0.4, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -17184,6 +17272,11 @@ for-in@^1.0.2: resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +foreach@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.6.tgz#87bcc8a1a0e74000ff2bf9802110708cfb02eb6e" + integrity sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg== + foreground-child@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" @@ -17602,6 +17695,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port-please@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.1.tgz#2556623cddb4801d823c0a6a15eec038abb483be" + integrity sha512-3UBAyM3u4ZBVYDsxOQfJDxEa6XTbpBDrOjp4mf7ExFRt5BKs/QywQQiJsh2B+hxcZLSapWqCRvElUe8DnKcFHA== + get-port@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" @@ -18097,7 +18195,7 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== -handlebars@4.7.8, handlebars@^4.7.7: +handlebars@4.7.8, handlebars@^4.7.6, handlebars@^4.7.7: version "4.7.8" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== @@ -18697,6 +18795,11 @@ http-signature@~1.3.6: jsprim "^2.0.2" sshpk "^1.14.1" +http2-client@^1.2.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/http2-client/-/http2-client-1.3.5.tgz#20c9dc909e3cc98284dd20af2432c524086df181" + integrity sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA== + http2-wrapper@^1.0.0-beta.5.2: version "1.0.0-beta.5.2" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3" @@ -20616,6 +20719,13 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-pointer@0.6.2, json-pointer@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.2.tgz#f97bd7550be5e9ea901f8c9264c9d436a22a93cd" + integrity sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw== + dependencies: + foreach "^2.0.4" + json-schema-to-ts@^2.9.1: version "2.9.1" resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-2.9.1.tgz#0e055b787587477abdb7e880c874efad3dba7779" @@ -21587,6 +21697,11 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + luxon@^1.25.0: version "1.28.1" resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.1.tgz#528cdf3624a54506d710290a2341aa8e6e6c61b0" @@ -21732,6 +21847,11 @@ marge@^1.0.1: dependencies: yargs "^3.15.0" +mark.js@^8.11.1: + version "8.11.1" + resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" + integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== + markdown-escapes@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.1.tgz#1994df2d3af4811de59a6714934c2b2292734518" @@ -21767,6 +21887,11 @@ markdown-table@^2.0.0: dependencies: repeat-string "^1.0.0" +marked@^4.0.15: + version "4.3.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== + material-colors@^1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" @@ -22456,6 +22581,23 @@ ml-tree-similarity@^1.0.0: binary-search "^1.3.5" num-sort "^2.0.0" +mobx-react-lite@^3.4.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz#3a4c22c30bfaa8b1b2aa48d12b2ba811c0947ab7" + integrity sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg== + +mobx-react@^7.2.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.6.0.tgz#ebf0456728a9bd2e5c24fdcf9b36e285a222a7d6" + integrity sha512-+HQUNuh7AoQ9ZnU6c4rvbiVVl+wEkb9WqYsVDzGLng+Dqj1XntHu79PvEWKtSMoMj67vFp/ZPXcElosuJO8ckA== + dependencies: + mobx-react-lite "^3.4.0" + +mobx@^6.0.4: + version "6.12.0" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.12.0.tgz#72b2685ca5af031aaa49e77a4d76ed67fcbf9135" + integrity sha512-Mn6CN6meXEnMa0a5u6a5+RKrqRedHBhZGd15AWLk9O6uFY4KYHzImdt8JI8WODo1bjTSRnwXhJox+FCUZhCKCQ== + mocha-junit-reporter@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-2.0.2.tgz#d521689b651dc52f52044739f8ffb368be415731" @@ -22937,6 +23079,13 @@ node-emoji@^1.10.0: dependencies: lodash.toarray "^4.4.0" +node-fetch-h2@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz#c6188325f9bd3d834020bf0f2d6dc17ced2241ac" + integrity sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg== + dependencies: + http2-client "^1.2.5" + node-fetch@^1.0.1, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -23052,6 +23201,13 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" +node-readfiles@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/node-readfiles/-/node-readfiles-0.2.0.tgz#dbbd4af12134e2e635c245ef93ffcf6f60673a5d" + integrity sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA== + dependencies: + es6-promise "^3.2.1" + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -23240,6 +23396,52 @@ nyc@15.1.0, nyc@^15.1.0: test-exclude "^6.0.0" yargs "^15.0.2" +oas-kit-common@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/oas-kit-common/-/oas-kit-common-1.0.8.tgz#6d8cacf6e9097967a4c7ea8bcbcbd77018e1f535" + integrity sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ== + dependencies: + fast-safe-stringify "^2.0.7" + +oas-linter@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/oas-linter/-/oas-linter-3.2.2.tgz#ab6a33736313490659035ca6802dc4b35d48aa1e" + integrity sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ== + dependencies: + "@exodus/schemasafe" "^1.0.0-rc.2" + should "^13.2.1" + yaml "^1.10.0" + +oas-resolver@^2.5.6: + version "2.5.6" + resolved "https://registry.yarnpkg.com/oas-resolver/-/oas-resolver-2.5.6.tgz#10430569cb7daca56115c915e611ebc5515c561b" + integrity sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ== + dependencies: + node-fetch-h2 "^2.3.0" + oas-kit-common "^1.0.8" + reftools "^1.1.9" + yaml "^1.10.0" + yargs "^17.0.1" + +oas-schema-walker@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz#74c3cd47b70ff8e0b19adada14455b5d3ac38a22" + integrity sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ== + +oas-validator@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/oas-validator/-/oas-validator-5.0.8.tgz#387e90df7cafa2d3ffc83b5fb976052b87e73c28" + integrity sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw== + dependencies: + call-me-maybe "^1.0.1" + oas-kit-common "^1.0.8" + oas-linter "^3.2.2" + oas-resolver "^2.5.6" + oas-schema-walker "^1.1.5" + reftools "^1.1.9" + should "^13.2.1" + yaml "^1.10.0" + object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -23477,6 +23679,14 @@ openai@^4.17.0, openai@^4.24.1: node-fetch "^2.6.7" web-streams-polyfill "^3.2.1" +openapi-sampler@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/openapi-sampler/-/openapi-sampler-1.4.0.tgz#c133cad6250481f2ec7e48b16eb70062adb514c0" + integrity sha512-3FKJQCHAMG9T7RsRy9u5Ft4ERPq1QQmn77C8T3OSofYL9uur59AqychvQ0YQKijrqRwIkAbzkh+nQnAE3gjMVA== + dependencies: + "@types/json-schema" "^7.0.7" + json-pointer "0.6.2" + openapi-types@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-10.0.0.tgz#0debbf663b2feed0322030b5b7c9080804076934" @@ -24086,6 +24296,11 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= +perfect-scrollbar@^1.5.5: + version "1.5.5" + resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz#41a211a2fb52a7191eff301432134ea47052b27f" + integrity sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -24335,7 +24550,7 @@ polished@^3.7.2: dependencies: "@babel/runtime" "^7.12.5" -polished@^4.2.2: +polished@^4.1.3, polished@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1" integrity sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ== @@ -24650,15 +24865,7 @@ postcss-values-parser@^6.0.2: is-url-superb "^4.0.0" quote-unquote "^1.0.0" -postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" - -postcss@^8.4.14, postcss@^8.4.23, postcss@^8.4.31: +postcss@8.4.31, postcss@^8.4.14, postcss@^8.4.23, postcss@^8.4.31: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== @@ -24667,6 +24874,14 @@ postcss@^8.4.14, postcss@^8.4.23, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + potpack@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/potpack/-/potpack-2.0.0.tgz#61f4dd2dc4b3d5e996e3698c0ec9426d0e169104" @@ -24810,7 +25025,12 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@^1.22.0, prismjs@~1.27.0: +prismjs@^1.22.0, prismjs@^1.27.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +prismjs@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== @@ -24918,7 +25138,7 @@ prompts@^2.0.1, prompts@^2.4.0, prompts@~2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@15.x, prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@15.x, prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -25184,6 +25404,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + queue-tick@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" @@ -25421,6 +25646,14 @@ react-docgen@^5.0.0: node-dir "^0.1.10" strip-indent "^3.0.0" +"react-dom@^17.0.0 || ^18.2.0": + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -25824,6 +26057,14 @@ react-syntax-highlighter@^15.3.1: prismjs "^1.22.0" refractor "^3.2.0" +react-tabs@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-4.3.0.tgz#9f4db0fd209ba4ab2c1e78993ff964435f84af62" + integrity sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q== + dependencies: + clsx "^1.1.0" + prop-types "^15.5.0" + "react-test-renderer@^16.8.0 || ^17.0.0", react-test-renderer@^17.0.0, react-test-renderer@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" @@ -25903,6 +26144,13 @@ react-window@^1.8.9: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" +"react@^17.0.0 || ^18.2.0": + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -26105,6 +26353,33 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redoc@~2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/redoc/-/redoc-2.1.3.tgz#612c9fed744993d5fc99cbf39fe9056bd1034fa5" + integrity sha512-d7F9qLLxaiFW4GC03VkwlX9wuRIpx9aiIIf3o6mzMnqPfhxrn2IRKGndrkJeVdItgCfmg9jXZiFEowm60f1meQ== + dependencies: + "@redocly/openapi-core" "^1.0.0-rc.2" + classnames "^2.3.1" + decko "^1.2.0" + dompurify "^2.2.8" + eventemitter3 "^4.0.7" + json-pointer "^0.6.2" + lunr "^2.3.9" + mark.js "^8.11.1" + marked "^4.0.15" + mobx-react "^7.2.0" + openapi-sampler "^1.3.1" + path-browserify "^1.0.1" + perfect-scrollbar "^1.5.5" + polished "^4.1.3" + prismjs "^1.27.0" + prop-types "^15.7.2" + react-tabs "^4.3.0" + slugify "~1.4.7" + stickyfill "^1.1.1" + swagger2openapi "^7.0.6" + url-template "^2.0.8" + reduce-reducers@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c" @@ -26179,6 +26454,11 @@ refractor@^3.2.0, refractor@^3.6.0: parse-entities "^2.0.0" prismjs "~1.27.0" +reftools@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/reftools/-/reftools-1.1.9.tgz#e16e19f662ccd4648605312c06d34e5da3a2b77e" + integrity sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w== + regedit@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/regedit/-/regedit-5.0.0.tgz#7ec444ef027cc704e104fae00586f84752291116" @@ -27039,6 +27319,13 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + schema-utils@2.7.0, schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.5, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" @@ -27323,7 +27610,7 @@ shallow-equal@^3.1.0: resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-3.1.0.tgz#e7a54bac629c7f248eff6c2f5b63122ba4320bec" integrity sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg== -shallowequal@^1.1.0: +shallowequal@1.1.0, shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== @@ -27389,6 +27676,50 @@ shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" +should-equal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" + integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA== + dependencies: + should-type "^1.4.0" + +should-format@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1" + integrity sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q== + dependencies: + should-type "^1.3.0" + should-type-adaptors "^1.0.1" + +should-type-adaptors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a" + integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA== + dependencies: + should-type "^1.3.0" + should-util "^1.0.0" + +should-type@^1.3.0, should-type@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3" + integrity sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ== + +should-util@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.1.tgz#fb0d71338f532a3a149213639e2d32cbea8bcb28" + integrity sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g== + +should@^13.2.1: + version "13.2.3" + resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10" + integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ== + dependencies: + should-equal "^2.0.0" + should-format "^3.0.3" + should-type "^1.4.0" + should-type-adaptors "^1.0.1" + should-util "^1.0.0" + side-channel@^1.0.2, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -27453,6 +27784,17 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simple-websocket@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-9.1.0.tgz#91cbb39eafefbe7e66979da6c639109352786a7f" + integrity sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ== + dependencies: + debug "^4.3.1" + queue-microtask "^1.2.2" + randombytes "^2.1.0" + readable-stream "^3.6.0" + ws "^7.4.2" + sinon@^7.4.2: version "7.5.0" resolved "https://registry.yarnpkg.com/sinon/-/sinon-7.5.0.tgz#e9488ea466070ea908fd44a3d6478fd4923c67ec" @@ -27518,6 +27860,11 @@ slide@~1.1.3: resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= +slugify@~1.4.7: + version "1.4.7" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.7.tgz#e42359d505afd84a44513280868e31202a79a628" + integrity sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg== + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -28060,6 +28407,11 @@ stats-lite@^2.2.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +stickyfill@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stickyfill/-/stickyfill-1.1.1.tgz#39413fee9d025c74a7e59ceecb23784cc0f17f02" + integrity sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA== + store2@^2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf" @@ -28398,6 +28750,21 @@ styled-components@^5.1.0: shallowequal "^1.1.0" supports-color "^5.5.0" +styled-components@^6.0.7: + version "6.1.6" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.6.tgz#c75c4f994136545b3bcc11608db5363710b78c0e" + integrity sha512-DgTLULSC29xpabJ24bbn1+hulU6vvGFQf4RPwBOJrm8WJFnN42yXpo5voBt3jDSJBa5tBd1L6PqswJjQ0wRKdg== + dependencies: + "@emotion/is-prop-valid" "1.2.1" + "@emotion/unitless" "0.8.0" + "@types/stylis" "4.2.0" + css-to-react-native "3.2.0" + csstype "3.1.2" + postcss "8.4.31" + shallowequal "1.1.0" + stylis "4.3.1" + tslib "2.5.0" + stylehacks@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" @@ -28473,6 +28840,11 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +stylis@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.1.tgz#ed8a9ebf9f76fe1e12d462f5cc3c4c980b23a7eb" + integrity sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ== + stylus-lookup@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-5.0.1.tgz#3c4d116c3b1e8e1a8169c0d9cd20e608595560f4" @@ -28592,6 +28964,23 @@ svgo@^2.7.0, svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" +swagger2openapi@^7.0.6: + version "7.0.8" + resolved "https://registry.yarnpkg.com/swagger2openapi/-/swagger2openapi-7.0.8.tgz#12c88d5de776cb1cbba758994930f40ad0afac59" + integrity sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g== + dependencies: + call-me-maybe "^1.0.1" + node-fetch "^2.6.1" + node-fetch-h2 "^2.3.0" + node-readfiles "^0.2.0" + oas-kit-common "^1.0.8" + oas-resolver "^2.5.6" + oas-schema-walker "^1.1.5" + oas-validator "^5.0.8" + reftools "^1.1.9" + yaml "^1.10.0" + yargs "^17.0.1" + symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -29272,6 +29661,11 @@ tslib@2.3.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -29892,6 +30286,11 @@ url-parse@^1.5.10, url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +url-template@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw== + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -31197,10 +31596,10 @@ ws@8.14.2, ws@>=8.14.2, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== -ws@^7.3.1: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== +ws@^7.3.1, ws@^7.4.2: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== x-default-browser@^0.4.0: version "0.4.0" @@ -31320,6 +31719,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml-ast-parser@0.0.43: + version "0.0.43" + resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" + integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== + yaml-language-server-parser@^0.1.0: version "0.1.3" resolved "https://registry.yarnpkg.com/yaml-language-server-parser/-/yaml-language-server-parser-0.1.3.tgz#f0e9082068291c7c330eefa1f3c9f1b4c3c54183" @@ -31376,7 +31780,20 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@17.7.2, yargs@^17.2.1, yargs@^17.3.1, yargs@^17.4.0, yargs@^17.7.1, yargs@^17.7.2: +yargs@17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" + integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@17.7.2, yargs@^17.0.1, yargs@^17.2.1, yargs@^17.3.1, yargs@^17.4.0, yargs@^17.7.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From d458b5382fdbf29e7f35a35fe2792aa86f4d6c4c Mon Sep 17 00:00:00 2001 From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:35:09 +0100 Subject: [PATCH 08/10] Remove legacy kibana react code editor (#171047) ## Summary This PR removes the legacy kibana react code-editor, alongside replacing all import declarations of this legacy component to the one offered by shared-ux, i.e import declaration source of `'@kbn/kibana-react/public'` is switched to `@kbn/code-editor`. Also in this PR an helper for writing jest tests has been included through the package `@kbn/code-editor-mock`, this would facilitate mocking the editor, especially given that the code editor leverages couple of APIs that are aren't included by default in jsdom, among them, `matchMedia`, `ResizeObserver`. The provided mock is sufficient for most use cases and can be setup in any package within kibana as a [`node_module` mock](https://jestjs.io/docs/manual-mocks#mocking-node-modules) without having to repeatedly manually mock the editor within individual test files. An example for how this might be done can be found here https://github.com/elastic/kibana/pull/171047/commits/ec5ba253688e952c6e601fb6e07de860e2a25c3a ### Checklist - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 3 +- .../public/editor/expression_editor.tsx | 2 +- examples/expressions_explorer/tsconfig.json | 1 + package.json | 3 +- .../components/field_input/code_editor.tsx | 5 +- .../components/field_input/tsconfig.json | 2 +- packages/kbn-monaco/index.ts | 10 +- packages/kbn-monaco/src/monaco_imports.ts | 14 +++ packages/kbn-monaco/src/typings.d.ts | 12 +++ .../src/text_based_languages_editor.tsx | 3 +- packages/kbn-text-based-editor/tsconfig.json | 3 +- packages/kbn-ui-shared-deps-src/BUILD.bazel | 1 + .../kbn-ui-shared-deps-src/src/definitions.js | 1 + packages/kbn-ui-shared-deps-src/src/entry.js | 1 + .../src/utils/get_render_cell_value.test.tsx | 3 +- .../shared-ux/code_editor/impl/BUILD.bazel | 37 +++++++ .../code_editor/{ => impl}/README.mdx | 0 .../__snapshots__/code_editor.test.tsx.snap | 34 ++++++- .../{ => impl}/code_editor.stories.tsx | 0 .../{ => impl}/code_editor.test.tsx | 20 +++- .../code_editor/{ => impl}/code_editor.tsx | 0 .../code_editor/{ => impl}/editor.styles.ts | 0 packages/shared-ux/code_editor/impl/index.tsx | 83 ++++++++++++++++ .../code_editor/{ => impl}/jest.config.js | 2 +- .../code_editor/{ => impl}/kibana.jsonc | 0 .../{ => impl}/languages/constants.ts | 0 .../{ => impl}/languages/css/constants.ts | 0 .../{ => impl}/languages/css/index.ts | 0 .../{ => impl}/languages/css/language.ts | 5 +- .../{ => impl}/languages/grok/constants.ts | 0 .../{ => impl}/languages/grok/index.ts | 0 .../languages/grok/language.test.ts | 0 .../{ => impl}/languages/grok/language.ts | 0 .../languages/handlebars/constants.ts | 0 .../{ => impl}/languages/handlebars/index.ts | 0 .../languages/handlebars/language.ts | 0 .../{ => impl}/languages/hjson/constants.ts | 0 .../{ => impl}/languages/hjson/index.ts | 0 .../{ => impl}/languages/hjson/language.ts | 0 .../code_editor/{ => impl}/languages/index.ts | 0 .../languages/markdown/constants.ts | 0 .../{ => impl}/languages/markdown/index.ts | 0 .../languages/markdown}/language.ts | 5 +- .../{ => impl}/languages/yaml/constants.ts | 0 .../{ => impl}/languages/yaml/index.ts | 0 .../languages/yaml}/language.ts | 5 +- .../code_editor/{ => impl}/mocks/storybook.ts | 0 .../code_editor/{ => impl}/package.json | 0 .../{ => impl}/placeholder_widget.ts | 0 .../{ => impl}/register_languages.ts | 0 .../code_editor/{ => impl}/remeasure_fonts.ts | 0 .../code_editor/{ => impl}/tsconfig.json | 5 +- .../shared-ux/code_editor/mocks/README.md | 23 +++++ .../code_editor/mocks/code_editor_mock.tsx | 28 ++++++ .../code_editor/{ => mocks}/index.ts | 2 +- .../code_editor/mocks/jest_helper.ts | 15 +-- .../shared-ux/code_editor/mocks/kibana.jsonc | 5 + .../monaco_mock/index.tsx} | 96 +++++++++++-------- .../shared-ux/code_editor/mocks/package.json | 6 ++ .../shared-ux/code_editor/mocks/tsconfig.json | 24 +++++ .../components/field/field.test.tsx | 1 + .../components/field/field_code_editor.tsx | 3 +- src/plugins/advanced_settings/tsconfig.json | 2 + .../components/actions/inspect_button.tsx | 7 +- src/plugins/data/tsconfig.json | 3 +- .../data_view_editor/public/shared_imports.ts | 9 +- src/plugins/data_view_editor/tsconfig.json | 1 + .../client_integration/helpers/jest.mocks.tsx | 4 +- .../public/shared_imports.ts | 8 +- .../data_view_field_editor/tsconfig.json | 1 + .../field_editor/field_editor.test.tsx | 4 +- .../components/field_editor/field_editor.tsx | 3 +- .../data_view_management/tsconfig.json | 1 + src/plugins/es_ui_shared/kibana.jsonc | 3 +- .../components/json_editor/json_editor.tsx | 2 +- src/plugins/es_ui_shared/tsconfig.json | 2 +- .../components/details/req_code_viewer.tsx | 3 +- src/plugins/inspector/tsconfig.json | 3 +- .../kibana_react/public/code_editor/README.md | 12 --- .../public/code_editor/code_editor_field.tsx | 38 -------- .../kibana_react/public/code_editor/index.tsx | 66 ------------- src/plugins/kibana_react/public/index.ts | 12 --- .../url_template_editor.stories.tsx | 2 +- .../url_template_editor.tsx | 3 +- src/plugins/kibana_react/tsconfig.json | 1 - .../components/expression_input/constants.ts | 2 +- .../expression_input/expression_input.tsx | 2 +- src/plugins/presentation_util/tsconfig.json | 3 +- .../object_view/components/inspect.tsx | 2 +- .../saved_objects_management/tsconfig.json | 1 + src/plugins/unified_doc_viewer/kibana.jsonc | 2 +- .../doc_viewer_source/source.test.tsx | 1 + .../json_code_editor_common.tsx | 2 +- src/plugins/unified_doc_viewer/tsconfig.json | 4 +- .../filter_editor/filter_editor.test.tsx | 4 +- .../filter_editor/filter_editor.tsx | 2 +- src/plugins/unified_search/tsconfig.json | 3 +- .../public/components/controls/raw_json.tsx | 2 +- src/plugins/vis_default_editor/tsconfig.json | 1 + .../components/timelion_expression_input.tsx | 3 +- src/plugins/vis_types/timelion/tsconfig.json | 1 + .../application/components/markdown_editor.js | 3 +- .../components/panel_config/markdown.tsx | 2 +- .../vis_types/timeseries/tsconfig.json | 1 + .../public/components/vega_vis_editor.tsx | 2 +- .../vega_inspector/components/spec_viewer.tsx | 2 +- src/plugins/vis_types/vega/tsconfig.json | 1 + tsconfig.base.json | 6 +- typings/index.d.ts | 5 - .../testing_embedded_lens/public/app.tsx | 3 +- .../testing_embedded_lens/tsconfig.json | 1 + .../cytoscape_example_data.stories.tsx | 2 +- .../settings_form/form_row_setting.tsx | 2 +- .../shared/monaco_code_editor/index.tsx | 2 +- x-pack/plugins/apm/tsconfig.json | 5 +- .../uis/arguments/editor.tsx | 2 +- .../uis/datasources/essql.js | 2 +- .../canvas_plugin_src/uis/views/markdown.js | 2 +- x-pack/plugins/canvas/tsconfig.json | 1 + .../control_settings/index.test.tsx | 3 +- .../control_yaml_view/index.test.tsx | 2 +- .../components/control_yaml_view/index.tsx | 2 +- .../components/policy_settings/index.test.tsx | 1 + .../public/test/test_provider.tsx | 2 +- x-pack/plugins/cloud_defend/tsconfig.json | 3 +- .../findings_flyout/json_tab.tsx | 2 +- .../vulnerability_finding_flyout.test.tsx | 1 + .../vulnerability_json_tab.tsx | 2 +- .../cloud_security_posture/tsconfig.json | 4 +- .../components/json_editor/json_editor.tsx | 2 +- x-pack/plugins/data_visualizer/tsconfig.json | 1 + .../sync_rules/advanced_sync_rules.tsx | 2 +- .../pipelines/ml_inference/test_pipeline.tsx | 2 +- .../shared/api_key/metadata_form.tsx | 2 +- .../api_key/security_privileges_form.tsx | 2 +- .../plugins/enterprise_search/tsconfig.json | 1 + .../components/import_complete_view.tsx | 3 +- x-pack/plugins/file_upload/tsconfig.json | 1 + .../package_policy_input_var_field.tsx | 2 +- .../sections/debug/components/code_block.tsx | 2 +- .../edit_output_flyout/index.test.tsx | 2 +- .../yaml_code_editor_with_placeholder.tsx | 3 +- x-pack/plugins/fleet/tsconfig.json | 1 + .../custom_patterns_input.js | 2 +- .../components/event_input/event_input.js | 2 +- .../components/pattern_input/pattern_input.js | 3 +- x-pack/plugins/grokdebugger/tsconfig.json | 1 + .../create_enrich_policy.test.tsx | 4 +- .../home/enrich_policies.test.tsx | 4 +- .../index_details_page.test.tsx | 4 +- .../template_create.test.tsx | 4 +- .../template_edit.test.tsx | 4 +- .../component_template_create.test.tsx | 4 +- .../component_template_edit.test.tsx | 8 +- .../helpers/mappings_editor.helpers.tsx | 4 +- .../helpers/setup_environment.tsx | 4 +- .../load_mappings_provider.test.tsx | 4 +- .../components/wizard_steps/step_aliases.tsx | 2 +- .../components/wizard_steps/step_settings.tsx | 2 +- .../details_flyout/policy_details_flyout.tsx | 2 +- .../details_page_settings_content.tsx | 2 +- x-pack/plugins/index_management/tsconfig.json | 1 + .../ingest_pipelines_create.test.tsx | 4 +- .../ingest_pipelines_create_from_csv.test.tsx | 4 +- .../pipeline_processors_editor.helpers.tsx | 4 +- .../__jest__/processors/processor.helpers.tsx | 4 +- .../__jest__/test_pipeline.helpers.tsx | 4 +- .../load_from_json/modal_provider.test.tsx | 4 +- .../pipelines_preview.tsx | 2 +- .../ingest_pipelines/public/shared_imports.ts | 3 +- x-pack/plugins/ingest_pipelines/tsconfig.json | 1 + .../formula/editor/formula_editor.tsx | 3 +- x-pack/plugins/lens/tsconfig.json | 2 + .../pipeline_editor/pipeline_editor.js | 2 +- x-pack/plugins/logstash/tsconfig.json | 1 + .../components/tile_request_tab.tsx | 2 +- .../components/vector_tile_inspector.tsx | 2 +- x-pack/plugins/maps/tsconfig.json | 1 + .../components/processor_configuration.tsx | 2 +- .../ml_inference/components/test_pipeline.tsx | 2 +- .../shared/on_failure_configuration.tsx | 2 +- .../create_analytics_advanced_editor.tsx | 2 +- .../runtime_mappings_editor.tsx | 2 +- .../ml_job_editor/ml_job_editor.tsx | 2 +- .../pipeline_details.tsx | 2 +- x-pack/plugins/ml/tsconfig.json | 1 + .../chat/chat_prompt_editor_function.tsx | 2 +- .../observability_ai_assistant/tsconfig.json | 3 +- .../plugins/osquery/public/editor/index.tsx | 2 +- x-pack/plugins/osquery/tsconfig.json | 3 +- .../public/application/components/editor.tsx | 2 +- .../components/output_pane/context_tab.tsx | 2 +- .../components/output_pane/parameters_tab.tsx | 2 +- x-pack/plugins/painless_lab/tsconfig.json | 1 + .../public/__jest__/setup_environment.tsx | 21 +--- .../runtime_fields/public/shared_imports.ts | 8 +- x-pack/plugins/runtime_fields/tsconfig.json | 2 + .../api_keys/api_keys_grid/api_key_flyout.tsx | 3 +- .../edit_role_mapping_page.test.tsx | 1 + .../json_rule_editor.test.tsx | 3 +- .../rule_editor_panel/json_rule_editor.tsx | 3 +- .../rule_editor_panel.test.tsx | 1 + .../es/index_privilege_form.test.tsx | 4 +- .../privileges/es/index_privilege_form.tsx | 2 +- .../privileges/es/index_privileges.test.tsx | 1 + x-pack/plugins/security/tsconfig.json | 6 +- .../components/api_key/metadata_form.tsx | 2 +- .../api_key/security_privileges_form.tsx | 2 +- .../plugins/serverless_search/tsconfig.json | 1 + .../client_integration/helpers/mocks.tsx | 19 +--- .../type_settings/hdfs_settings.tsx | 2 +- .../steps/step_settings.tsx | 2 +- x-pack/plugins/snapshot_restore/tsconfig.json | 2 + .../expression/es_query_expression.test.tsx | 12 ++- .../expression/es_query_expression.tsx | 2 +- .../es_query/expression/expression.test.tsx | 4 +- x-pack/plugins/stack_alerts/tsconfig.json | 1 + .../__mocks__/@kbn/code-editor/index.tsx | 8 ++ .../connector_types/bedrock/params.test.tsx | 12 --- .../cases_webhook/webhook_connectors.test.tsx | 12 --- .../cases_webhook/webhook_params.test.tsx | 17 +--- .../d3security/params.test.tsx | 12 --- .../es_index/es_index_params.test.tsx | 13 --- .../connector_types/openai/params.test.tsx | 12 --- .../opsgenie/create_alert/index.test.tsx | 13 --- .../create_alert/json_editor.test.tsx | 13 --- .../connector_types/opsgenie/params.test.tsx | 12 --- .../tines/tines_params.test.tsx | 12 --- .../connector_types/torq/torq_params.test.tsx | 13 --- .../webhook/webhook_params.test.tsx | 13 --- x-pack/plugins/stack_connectors/tsconfig.json | 2 + .../__mocks__/@kbn/code-editor/index.ts | 8 ++ .../monitor_add_edit/fields/code_editor.tsx | 2 +- .../fields/request_body_field.test.tsx | 26 +---- .../fields/source_field.test.tsx | 17 ---- .../monitor_add_page.test.tsx | 17 ---- .../monitor_edit_page.test.tsx | 17 ---- x-pack/plugins/synthetics/tsconfig.json | 5 +- .../advanced_pivot_editor.tsx | 2 +- .../advanced_runtime_mappings_editor.tsx | 2 +- .../advanced_source_editor.tsx | 2 +- .../filter_agg/components/editor_form.tsx | 2 +- x-pack/plugins/transform/tsconfig.json | 3 +- .../public/application/code_editor.mock.tsx | 35 ------- ...son_editor_with_message_variables.test.tsx | 10 +- .../json_editor_with_message_variables.tsx | 2 +- .../plugins/triggers_actions_ui/tsconfig.json | 4 +- ...ics_edit_policy_extension_wrapper.test.tsx | 4 +- .../watch_create_json_page.test.tsx | 4 +- .../watch_create_threshold_page.test.tsx | 4 +- .../watch_edit_page.test.tsx | 4 +- .../json_watch_edit/json_watch_edit_form.tsx | 2 +- .../json_watch_edit_simulate.tsx | 2 +- .../action_fields/webhook_action_fields.tsx | 2 +- x-pack/plugins/watcher/tsconfig.json | 1 + yarn.lock | 6 +- 256 files changed, 671 insertions(+), 696 deletions(-) create mode 100644 packages/kbn-monaco/src/typings.d.ts create mode 100644 packages/shared-ux/code_editor/impl/BUILD.bazel rename packages/shared-ux/code_editor/{ => impl}/README.mdx (100%) rename packages/shared-ux/code_editor/{ => impl}/__snapshots__/code_editor.test.tsx.snap (88%) rename packages/shared-ux/code_editor/{ => impl}/code_editor.stories.tsx (100%) rename packages/shared-ux/code_editor/{ => impl}/code_editor.test.tsx (94%) rename packages/shared-ux/code_editor/{ => impl}/code_editor.tsx (100%) rename packages/shared-ux/code_editor/{ => impl}/editor.styles.ts (100%) create mode 100644 packages/shared-ux/code_editor/impl/index.tsx rename packages/shared-ux/code_editor/{ => impl}/jest.config.js (94%) rename packages/shared-ux/code_editor/{ => impl}/kibana.jsonc (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/css/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/css/index.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/css/language.ts (64%) rename packages/shared-ux/code_editor/{ => impl}/languages/grok/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/grok/index.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/grok/language.test.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/grok/language.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/handlebars/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/handlebars/index.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/handlebars/language.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/hjson/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/hjson/index.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/hjson/language.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/index.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/markdown/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/markdown/index.ts (100%) rename packages/shared-ux/code_editor/{languages/yaml => impl/languages/markdown}/language.ts (64%) rename packages/shared-ux/code_editor/{ => impl}/languages/yaml/constants.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/languages/yaml/index.ts (100%) rename packages/shared-ux/code_editor/{languages/markdown => impl/languages/yaml}/language.ts (63%) rename packages/shared-ux/code_editor/{ => impl}/mocks/storybook.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/package.json (100%) rename packages/shared-ux/code_editor/{ => impl}/placeholder_widget.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/register_languages.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/remeasure_fonts.ts (100%) rename packages/shared-ux/code_editor/{ => impl}/tsconfig.json (80%) create mode 100644 packages/shared-ux/code_editor/mocks/README.md create mode 100644 packages/shared-ux/code_editor/mocks/code_editor_mock.tsx rename packages/shared-ux/code_editor/{ => mocks}/index.ts (84%) rename src/plugins/kibana_react/public/code_editor/code_editor.tsx => packages/shared-ux/code_editor/mocks/jest_helper.ts (58%) create mode 100644 packages/shared-ux/code_editor/mocks/kibana.jsonc rename packages/shared-ux/code_editor/{code_editor.test.helpers.tsx => mocks/monaco_mock/index.tsx} (66%) create mode 100644 packages/shared-ux/code_editor/mocks/package.json create mode 100644 packages/shared-ux/code_editor/mocks/tsconfig.json delete mode 100644 src/plugins/kibana_react/public/code_editor/README.md delete mode 100644 src/plugins/kibana_react/public/code_editor/code_editor_field.tsx delete mode 100644 src/plugins/kibana_react/public/code_editor/index.tsx create mode 100644 x-pack/plugins/stack_connectors/__mocks__/@kbn/code-editor/index.tsx create mode 100644 x-pack/plugins/synthetics/__mocks__/@kbn/code-editor/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/code_editor.mock.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4dae217dce6ad..d2d3d6b0f1b33 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -86,7 +86,8 @@ x-pack/test/cloud_integration/plugins/saml_provider @elastic/kibana-core x-pack/plugins/cloud_integrations/cloud_links @elastic/kibana-core x-pack/plugins/cloud @elastic/kibana-core x-pack/plugins/cloud_security_posture @elastic/kibana-cloud-security-posture -packages/shared-ux/code_editor @elastic/appex-sharedux +packages/shared-ux/code_editor/impl @elastic/appex-sharedux +packages/shared-ux/code_editor/mocks @elastic/appex-sharedux packages/kbn-code-owners @elastic/appex-qa packages/kbn-coloring @elastic/kibana-visualizations packages/kbn-config @elastic/kibana-core diff --git a/examples/expressions_explorer/public/editor/expression_editor.tsx b/examples/expressions_explorer/public/editor/expression_editor.tsx index c940a21fada27..998a9e486ba19 100644 --- a/examples/expressions_explorer/public/editor/expression_editor.tsx +++ b/examples/expressions_explorer/public/editor/expression_editor.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { CodeEditor } from '@kbn/kibana-react-plugin/public'; +import { CodeEditor } from '@kbn/code-editor'; interface Props { value: string; diff --git a/examples/expressions_explorer/tsconfig.json b/examples/expressions_explorer/tsconfig.json index 717a797173597..b75817c68be7c 100644 --- a/examples/expressions_explorer/tsconfig.json +++ b/examples/expressions_explorer/tsconfig.json @@ -22,5 +22,6 @@ "@kbn/i18n", "@kbn/i18n-react", "@kbn/core-ui-settings-browser", + "@kbn/code-editor", ] } diff --git a/package.json b/package.json index 23e49b0d4cb35..22ac585845216 100644 --- a/package.json +++ b/package.json @@ -192,7 +192,8 @@ "@kbn/cloud-links-plugin": "link:x-pack/plugins/cloud_integrations/cloud_links", "@kbn/cloud-plugin": "link:x-pack/plugins/cloud", "@kbn/cloud-security-posture-plugin": "link:x-pack/plugins/cloud_security_posture", - "@kbn/code-editor": "link:packages/shared-ux/code_editor", + "@kbn/code-editor": "link:packages/shared-ux/code_editor/impl", + "@kbn/code-editor-mock": "link:packages/shared-ux/code_editor/mocks", "@kbn/coloring": "link:packages/kbn-coloring", "@kbn/config": "link:packages/kbn-config", "@kbn/config-mocks": "link:packages/kbn-config-mocks", diff --git a/packages/kbn-management/settings/components/field_input/code_editor.tsx b/packages/kbn-management/settings/components/field_input/code_editor.tsx index 3f46778917fdd..ac1ea672d8a15 100644 --- a/packages/kbn-management/settings/components/field_input/code_editor.tsx +++ b/packages/kbn-management/settings/components/field_input/code_editor.tsx @@ -15,11 +15,12 @@ import React, { useCallback } from 'react'; import { monaco, XJsonLang } from '@kbn/monaco'; + import { CodeEditor as KibanaReactCodeEditor, - MarkdownLang, type CodeEditorProps as KibanaReactCodeEditorProps, -} from '@kbn/kibana-react-plugin/public'; + MarkdownLang, +} from '@kbn/code-editor'; type Props = Pick; type Options = KibanaReactCodeEditorProps['options']; diff --git a/packages/kbn-management/settings/components/field_input/tsconfig.json b/packages/kbn-management/settings/components/field_input/tsconfig.json index bb4c6b4aa57d0..d971549abb2d4 100644 --- a/packages/kbn-management/settings/components/field_input/tsconfig.json +++ b/packages/kbn-management/settings/components/field_input/tsconfig.json @@ -19,7 +19,6 @@ "@kbn/management-settings-types", "@kbn/management-settings-field-definition", "@kbn/monaco", - "@kbn/kibana-react-plugin", "@kbn/management-settings-utilities", "@kbn/i18n-react", "@kbn/i18n", @@ -30,5 +29,6 @@ "@kbn/core-i18n-browser", "@kbn/core-analytics-browser-mocks", "@kbn/core-ui-settings-browser", + "@kbn/code-editor", ] } diff --git a/packages/kbn-monaco/index.ts b/packages/kbn-monaco/index.ts index 2ebb05bd0e393..e2e3c32d9d0fd 100644 --- a/packages/kbn-monaco/index.ts +++ b/packages/kbn-monaco/index.ts @@ -8,7 +8,15 @@ import './src/register_globals'; -export { monaco } from './src/monaco_imports'; +export { + monaco, + cssConf, + cssLanguage, + markdownConf, + markdownLanguage, + yamlConf, + yamlLanguage, +} from './src/monaco_imports'; export { XJsonLang } from './src/xjson'; export { SQLLang } from './src/sql'; export { ESQL_LANG_ID, ESQL_THEME_ID, ESQLLang } from './src/esql'; diff --git a/packages/kbn-monaco/src/monaco_imports.ts b/packages/kbn-monaco/src/monaco_imports.ts index 9da2a3f4562f3..cebdb7ffa1045 100644 --- a/packages/kbn-monaco/src/monaco_imports.ts +++ b/packages/kbn-monaco/src/monaco_imports.ts @@ -28,4 +28,18 @@ import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution. import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution.js'; // Needed for basic xml support import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'; // Needed for yaml support +// config for supported base languages +export { + conf as cssConf, + language as cssLanguage, +} from 'monaco-editor/esm/vs/basic-languages/css/css'; +export { + conf as markdownConf, + language as markdownLanguage, +} from 'monaco-editor/esm/vs/basic-languages/markdown/markdown'; +export { + conf as yamlConf, + language as yamlLanguage, +} from 'monaco-editor/esm/vs/basic-languages/yaml/yaml'; + export { monaco }; diff --git a/packages/kbn-monaco/src/typings.d.ts b/packages/kbn-monaco/src/typings.d.ts new file mode 100644 index 0000000000000..64301b9a59683 --- /dev/null +++ b/packages/kbn-monaco/src/typings.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright 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. + */ + +// Monaco languages support +declare module 'monaco-editor/esm/vs/basic-languages/markdown/markdown'; +declare module 'monaco-editor/esm/vs/basic-languages/css/css'; +declare module 'monaco-editor/esm/vs/basic-languages/yaml/yaml'; 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 24966c78960bb..3546fcec41af4 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 @@ -39,8 +39,7 @@ import { EuiOutsideClickDetector, EuiToolTip, } from '@elastic/eui'; -import { CodeEditor } from '@kbn/kibana-react-plugin/public'; -import type { CodeEditorProps } from '@kbn/kibana-react-plugin/public'; +import { CodeEditor, CodeEditorProps } from '@kbn/code-editor'; import { textBasedLanguagedEditorStyles, diff --git a/packages/kbn-text-based-editor/tsconfig.json b/packages/kbn-text-based-editor/tsconfig.json index 72240c8aa060d..1c71fc544155d 100644 --- a/packages/kbn-text-based-editor/tsconfig.json +++ b/packages/kbn-text-based-editor/tsconfig.json @@ -24,7 +24,8 @@ "@kbn/expressions-plugin", "@kbn/data-views-plugin", "@kbn/index-management-plugin", - "@kbn/visualization-utils" + "@kbn/visualization-utils", + "@kbn/code-editor", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-ui-shared-deps-src/BUILD.bazel b/packages/kbn-ui-shared-deps-src/BUILD.bazel index cd97c193f9f86..9e62a7418f153 100644 --- a/packages/kbn-ui-shared-deps-src/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-src/BUILD.bazel @@ -35,6 +35,7 @@ webpack_cli( "//packages/kbn-peggy-loader", "//packages/shared-ux/error_boundary", "//packages/kbn-rison", + "//packages/shared-ux/code_editor/impl:code_editor", ], output_dir = True, args = [ diff --git a/packages/kbn-ui-shared-deps-src/src/definitions.js b/packages/kbn-ui-shared-deps-src/src/definitions.js index 9ae258dca6bc8..7480303087b6c 100644 --- a/packages/kbn-ui-shared-deps-src/src/definitions.js +++ b/packages/kbn-ui-shared-deps-src/src/definitions.js @@ -98,6 +98,7 @@ const externals = { classnames: '__kbnSharedDeps__.Classnames', '@tanstack/react-query': '__kbnSharedDeps__.ReactQuery', '@tanstack/react-query-devtools': '__kbnSharedDeps__.ReactQueryDevtools', + '@kbn/code-editor': '__kbnSharedDeps__.KbnCodeEditor', }; module.exports = { distDir, jsFilename, cssDistFilename, externals }; diff --git a/packages/kbn-ui-shared-deps-src/src/entry.js b/packages/kbn-ui-shared-deps-src/src/entry.js index 6ba856cbbc2d1..d0585d0cacc04 100644 --- a/packages/kbn-ui-shared-deps-src/src/entry.js +++ b/packages/kbn-ui-shared-deps-src/src/entry.js @@ -74,3 +74,4 @@ export const History = require('history'); export const Classnames = require('classnames'); export const ReactQuery = require('@tanstack/react-query'); export const ReactQueryDevtools = require('@tanstack/react-query-devtools'); +export const KbnCodeEditor = require('@kbn/code-editor'); diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx index 7db7ffedfdecf..c59acdc815389 100644 --- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.test.tsx @@ -13,7 +13,8 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { getRenderCellValueFn } from './get_render_cell_value'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { CodeEditorProps, KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { CodeEditorProps } from '@kbn/code-editor'; import { buildDataTableRecord } from '@kbn/discover-utils'; import type { EsHitRecord } from '@kbn/discover-utils/types'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; diff --git a/packages/shared-ux/code_editor/impl/BUILD.bazel b/packages/shared-ux/code_editor/impl/BUILD.bazel new file mode 100644 index 0000000000000..ad571cb379afd --- /dev/null +++ b/packages/shared-ux/code_editor/impl/BUILD.bazel @@ -0,0 +1,37 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") + +SRCS = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/test_helpers.ts", + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +BUNDLER_DEPS = [ + "@npm//react", + "@npm//tslib", + "@npm//react-monaco-editor", + "@npm//react-resize-detector", +] + +js_library( + name = "code_editor", + package_name = "@kbn/code-editor", + srcs = SRCS + ["package.json"], + deps = BUNDLER_DEPS, + visibility = ["//visibility:public"], +) diff --git a/packages/shared-ux/code_editor/README.mdx b/packages/shared-ux/code_editor/impl/README.mdx similarity index 100% rename from packages/shared-ux/code_editor/README.mdx rename to packages/shared-ux/code_editor/impl/README.mdx diff --git a/packages/shared-ux/code_editor/__snapshots__/code_editor.test.tsx.snap b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap similarity index 88% rename from packages/shared-ux/code_editor/__snapshots__/code_editor.test.tsx.snap rename to packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap index 790a4f05f8c50..4fb960f82cc7d 100644 --- a/packages/shared-ux/code_editor/__snapshots__/code_editor.test.tsx.snap +++ b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap @@ -290,7 +290,7 @@ exports[` is rendered 1`] = ` - is rendered 1`] = `