From c6dd76e76c4bad4c6f0a78dda86b0cc67eb31ca8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Georgiana-Andreea=20Onolea=C8=9B=C4=83?=
Date: Tue, 8 Oct 2024 16:10:24 +0300
Subject: [PATCH 01/50] [ResponseOps][Cases]Design Review changes PR 3
(#194936)
## Summary
Closes https://github.com/elastic/kibana/issues/188187
- The fly out title now updates appropriately when editing an existing
field or template, displaying "Edit field" and "Edit template"
respectively.
https://github.com/user-attachments/assets/b85d7d27-ba89-4fbd-97c4-dc5873dda6d8
---
.../components/configure_cases/index.test.tsx | 12 ++++++++++++
.../public/components/configure_cases/index.tsx | 8 ++++++--
.../components/configure_cases/translations.ts | 13 ++++++++++++-
3 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
index 7f375ef75938bc..6c65eae41c78bc 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx
@@ -902,6 +902,10 @@ describe('ConfigureCases', () => {
expect(await screen.findByTestId('common-flyout')).toBeInTheDocument();
+ expect(await screen.findByTestId('common-flyout-header')).toHaveTextContent(
+ i18n.EDIT_CUSTOM_FIELD
+ );
+
await userEvent.click(screen.getByTestId('custom-field-label-input'));
await userEvent.paste('!!');
await userEvent.click(screen.getByTestId('text-custom-field-required'));
@@ -941,6 +945,10 @@ describe('ConfigureCases', () => {
await userEvent.click(screen.getByTestId('add-custom-field'));
expect(await screen.findByTestId('common-flyout')).toBeInTheDocument();
+
+ expect(await screen.findByTestId('common-flyout-header')).toHaveTextContent(
+ i18n.ADD_CUSTOM_FIELD
+ );
});
it('closes fly out for when click on cancel', async () => {
@@ -1177,6 +1185,10 @@ describe('ConfigureCases', () => {
expect(await screen.findByTestId('common-flyout')).toBeInTheDocument();
+ expect(await screen.findByTestId('common-flyout-header')).toHaveTextContent(
+ i18n.EDIT_TEMPLATE
+ );
+
await userEvent.clear(await screen.findByTestId('template-name-input'));
await userEvent.click(await screen.findByTestId('template-name-input'));
await userEvent.paste('Updated template name');
diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx
index d26fe1ee185f84..61f99a46a0b08c 100644
--- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx
+++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx
@@ -484,7 +484,9 @@ export const ConfigureCases: React.FC = React.memo(() => {
}
onCloseFlyout={onCloseCustomFieldFlyout}
onSaveField={onCustomFieldSave}
- renderHeader={() => {i18n.ADD_CUSTOM_FIELD}}
+ renderHeader={() => (
+ {customFieldToEdit ? i18n.EDIT_CUSTOM_FIELD : i18n.ADD_CUSTOM_FIELD}
+ )}
>
{({ onChange }) => (
@@ -504,7 +506,9 @@ export const ConfigureCases: React.FC = React.memo(() => {
}
onCloseFlyout={onCloseTemplateFlyout}
onSaveField={onTemplateSave}
- renderHeader={() => {i18n.CREATE_TEMPLATE}}
+ renderHeader={() => (
+ {templateToEdit ? i18n.EDIT_TEMPLATE : i18n.CREATE_TEMPLATE}
+ )}
>
{({ onChange }) => (
Date: Tue, 8 Oct 2024 15:13:37 +0200
Subject: [PATCH 02/50] [Search] Add UI kbn feature flag for Inference
Endpoints (#195385)
This adds a UI feature flag for the inference endpoints plugin.
Call this to enable:
```
POST kbn:/internal/kibana/settings/inferenceEndpointsUi:enabled
{"value": true}
```
---
x-pack/plugins/search_inference_endpoints/public/index.ts | 2 ++
.../plugins/search_inference_endpoints/public/plugin.ts | 8 ++++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/search_inference_endpoints/public/index.ts b/x-pack/plugins/search_inference_endpoints/public/index.ts
index b06f1f64b909da..1c9e267a18bdec 100644
--- a/x-pack/plugins/search_inference_endpoints/public/index.ts
+++ b/x-pack/plugins/search_inference_endpoints/public/index.ts
@@ -17,3 +17,5 @@ export type {
SearchInferenceEndpointsPluginSetup,
SearchInferenceEndpointsPluginStart,
} from './types';
+
+export const INFERENCE_ENDPOINTS_UI_FLAG = 'inferenceEndpointsUi:enabled';
diff --git a/x-pack/plugins/search_inference_endpoints/public/plugin.ts b/x-pack/plugins/search_inference_endpoints/public/plugin.ts
index ab49281f82e4c4..a864b7c0fcdd62 100644
--- a/x-pack/plugins/search_inference_endpoints/public/plugin.ts
+++ b/x-pack/plugins/search_inference_endpoints/public/plugin.ts
@@ -21,6 +21,7 @@ import {
SearchInferenceEndpointsPluginSetup,
SearchInferenceEndpointsPluginStart,
} from './types';
+import { INFERENCE_ENDPOINTS_UI_FLAG } from '.';
export class SearchInferenceEndpointsPlugin
implements Plugin
@@ -34,8 +35,11 @@ export class SearchInferenceEndpointsPlugin
public setup(
core: CoreSetup
): SearchInferenceEndpointsPluginSetup {
- if (!this.config.ui?.enabled) return {};
-
+ if (
+ !this.config.ui?.enabled &&
+ !core.uiSettings.get(INFERENCE_ENDPOINTS_UI_FLAG, false)
+ )
+ return {};
core.application.register({
id: PLUGIN_ID,
appRoute: '/app/search_inference_endpoints',
From cb9f998c064f4519afd9e7bbf73c9c55ef7059b3 Mon Sep 17 00:00:00 2001
From: Ash <1849116+ashokaditya@users.noreply.github.com>
Date: Tue, 8 Oct 2024 15:17:12 +0200
Subject: [PATCH 03/50] [DataUsage] Add internal API/UX hooks to interact with
serverless project metrics API (#193966)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Adds the bulk of the code for new [Data Usage
plugin](https://github.com/elastic/kibana/pull/193466). Plugin is still
disabled by default and will be enabled later for serverless only.
- UI components (filters, charts)
- Adds an internal API route that provides visualization data to render
data usage charts for serverless.
### request example
```json5
GET /internal/api/data_usage/metrics?from=1726858530000&to=1726908930000&size=0&metricTypes=ingest_rate&metricTypes=storage_retained&dataStreams=ds_name_1&dataStreams=ds_name_2
```
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
---------
Co-authored-by: neptunian
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../cards_navigation/src/consts.tsx | 2 +-
x-pack/plugins/data_usage/common/index.ts | 4 +
.../data_usage/common/query_client.tsx | 57 +++++
.../rest_types/data_streams.ts} | 18 +-
.../data_usage/common/rest_types/index.ts | 9 +
.../common/rest_types/usage_metrics.test.ts | 179 +++++++++++++
.../common/rest_types/usage_metrics.ts | 102 ++++++++
x-pack/plugins/data_usage/common/types.ts | 9 +
x-pack/plugins/data_usage/kibana.jsonc | 20 +-
.../public/app/components/chart_panel.tsx | 123 +++++++++
.../public/app/components/charts.tsx | 36 +++
.../app/components/dataset_quality_link.tsx | 44 ++++
.../public/app/components/date_picker.tsx | 88 +++++++
.../public/app/components/legend_action.tsx | 89 +++++++
.../data_usage/public/app/data_usage.tsx | 147 +++++++++++
.../app/hooks/use_charts_url_params.tsx | 136 ++++++++++
.../public/app/hooks/use_date_picker.tsx | 98 ++++++++
x-pack/plugins/data_usage/public/app/types.ts | 24 ++
.../plugins/data_usage/public/application.tsx | 8 +-
.../public/hooks/use_get_usage_metrics.ts | 45 ++++
.../data_usage/public/hooks/use_url_params.ts | 30 +++
.../public/utils/use_breadcrumbs.tsx | 28 +++
.../data_usage/server/common/errors.ts | 18 ++
x-pack/plugins/data_usage/server/config.ts | 13 +-
x-pack/plugins/data_usage/server/index.ts | 8 +-
x-pack/plugins/data_usage/server/plugin.ts | 42 +++-
.../data_usage/server/routes/error_handler.ts | 40 +++
.../data_usage/server/routes/index.tsx | 17 ++
.../server/routes/internal/data_streams.ts | 37 +++
.../routes/internal/data_streams_handler.ts | 43 ++++
.../server/routes/internal/index.tsx | 9 +
.../server/routes/internal/usage_metrics.ts | 37 +++
.../routes/internal/usage_metrics_handler.ts | 237 ++++++++++++++++++
.../plugins/data_usage/server/types/index.ts | 8 +
.../plugins/data_usage/server/types/types.ts | 42 ++++
.../server/utils/custom_http_request_error.ts | 22 ++
.../plugins/data_usage/server/utils/index.ts | 8 +
x-pack/plugins/data_usage/tsconfig.json | 7 +
38 files changed, 1859 insertions(+), 25 deletions(-)
create mode 100644 x-pack/plugins/data_usage/common/query_client.tsx
rename x-pack/plugins/data_usage/{server/types.ts => common/rest_types/data_streams.ts} (51%)
create mode 100644 x-pack/plugins/data_usage/common/rest_types/index.ts
create mode 100644 x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts
create mode 100644 x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts
create mode 100644 x-pack/plugins/data_usage/common/types.ts
create mode 100644 x-pack/plugins/data_usage/public/app/components/chart_panel.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/components/charts.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/components/date_picker.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/components/legend_action.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/data_usage.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx
create mode 100644 x-pack/plugins/data_usage/public/app/types.ts
create mode 100644 x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts
create mode 100644 x-pack/plugins/data_usage/public/hooks/use_url_params.ts
create mode 100644 x-pack/plugins/data_usage/public/utils/use_breadcrumbs.tsx
create mode 100644 x-pack/plugins/data_usage/server/common/errors.ts
create mode 100644 x-pack/plugins/data_usage/server/routes/error_handler.ts
create mode 100644 x-pack/plugins/data_usage/server/routes/index.tsx
create mode 100644 x-pack/plugins/data_usage/server/routes/internal/data_streams.ts
create mode 100644 x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts
create mode 100644 x-pack/plugins/data_usage/server/routes/internal/index.tsx
create mode 100644 x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts
create mode 100644 x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts
create mode 100644 x-pack/plugins/data_usage/server/types/index.ts
create mode 100644 x-pack/plugins/data_usage/server/types/types.ts
create mode 100644 x-pack/plugins/data_usage/server/utils/custom_http_request_error.ts
create mode 100644 x-pack/plugins/data_usage/server/utils/index.ts
diff --git a/packages/kbn-management/cards_navigation/src/consts.tsx b/packages/kbn-management/cards_navigation/src/consts.tsx
index 16e655c5510ad0..6a22b9e33d620e 100644
--- a/packages/kbn-management/cards_navigation/src/consts.tsx
+++ b/packages/kbn-management/cards_navigation/src/consts.tsx
@@ -77,7 +77,7 @@ export const appDefinitions: Record = {
description: i18n.translate('management.landing.withCardNavigation.dataUsageDescription', {
defaultMessage: 'View data usage and retention.',
}),
- icon: 'documents',
+ icon: 'stats',
},
[AppIds.RULES]: {
diff --git a/x-pack/plugins/data_usage/common/index.ts b/x-pack/plugins/data_usage/common/index.ts
index 4b6f899b58d377..eb0787f53f344e 100644
--- a/x-pack/plugins/data_usage/common/index.ts
+++ b/x-pack/plugins/data_usage/common/index.ts
@@ -11,3 +11,7 @@ export const PLUGIN_ID = 'data_usage';
export const PLUGIN_NAME = i18n.translate('xpack.dataUsage.name', {
defaultMessage: 'Data Usage',
});
+
+export const DATA_USAGE_API_ROUTE_PREFIX = '/api/data_usage/';
+export const DATA_USAGE_METRICS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}metrics`;
+export const DATA_USAGE_DATA_STREAMS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}data_streams`;
diff --git a/x-pack/plugins/data_usage/common/query_client.tsx b/x-pack/plugins/data_usage/common/query_client.tsx
new file mode 100644
index 00000000000000..8a64ed7a51349a
--- /dev/null
+++ b/x-pack/plugins/data_usage/common/query_client.tsx
@@ -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 type { PropsWithChildren } from 'react';
+import React, { memo, useMemo } from 'react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+type QueryClientOptionsProp = ConstructorParameters[0];
+
+/**
+ * Default Query Client for Data Usage.
+ */
+export class DataUsageQueryClient extends QueryClient {
+ constructor(options: QueryClientOptionsProp = {}) {
+ const optionsWithDefaults: QueryClientOptionsProp = {
+ ...options,
+ defaultOptions: {
+ ...(options.defaultOptions ?? {}),
+ queries: {
+ refetchIntervalInBackground: false,
+ refetchOnWindowFocus: false,
+ refetchOnMount: true,
+ keepPreviousData: true,
+ ...(options?.defaultOptions?.queries ?? {}),
+ },
+ },
+ };
+ super(optionsWithDefaults);
+ }
+}
+
+/**
+ * The default Data Usage Query Client. Can be imported and used from outside of React hooks
+ * and still benefit from ReactQuery features (like caching, etc)
+ *
+ * @see https://tanstack.com/query/v4/docs/reference/QueryClient
+ */
+export const dataUsageQueryClient = new DataUsageQueryClient();
+
+export type ReactQueryClientProviderProps = PropsWithChildren<{
+ queryClient?: DataUsageQueryClient;
+}>;
+
+export const DataUsageReactQueryClientProvider = memo(
+ ({ queryClient, children }) => {
+ const client = useMemo(() => {
+ return queryClient || dataUsageQueryClient;
+ }, [queryClient]);
+ return {children};
+ }
+);
+
+DataUsageReactQueryClientProvider.displayName = 'DataUsageReactQueryClientProvider';
diff --git a/x-pack/plugins/data_usage/server/types.ts b/x-pack/plugins/data_usage/common/rest_types/data_streams.ts
similarity index 51%
rename from x-pack/plugins/data_usage/server/types.ts
rename to x-pack/plugins/data_usage/common/rest_types/data_streams.ts
index 9f43ae2d3c2983..b1c02bb40854d1 100644
--- a/x-pack/plugins/data_usage/server/types.ts
+++ b/x-pack/plugins/data_usage/common/rest_types/data_streams.ts
@@ -5,12 +5,14 @@
* 2.0.
*/
-/* eslint-disable @typescript-eslint/no-empty-interface*/
+import { schema } from '@kbn/config-schema';
-export interface DataUsageSetupDependencies {}
-
-export interface DataUsageStartDependencies {}
-
-export interface DataUsageServerSetup {}
-
-export interface DataUsageServerStart {}
+export const DataStreamsResponseSchema = {
+ body: () =>
+ schema.arrayOf(
+ schema.object({
+ name: schema.string(),
+ storageSizeBytes: schema.number(),
+ })
+ ),
+};
diff --git a/x-pack/plugins/data_usage/common/rest_types/index.ts b/x-pack/plugins/data_usage/common/rest_types/index.ts
new file mode 100644
index 00000000000000..64b5c640ebbb5f
--- /dev/null
+++ b/x-pack/plugins/data_usage/common/rest_types/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './usage_metrics';
+export * from './data_streams';
diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts
new file mode 100644
index 00000000000000..f6c08e2caddc00
--- /dev/null
+++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts
@@ -0,0 +1,179 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { UsageMetricsRequestSchema } from './usage_metrics';
+
+describe('usage_metrics schemas', () => {
+ it('should accept valid request query', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained'],
+ })
+ ).not.toThrow();
+ });
+
+ it('should accept a single `metricTypes` in request query', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: 'ingest_rate',
+ })
+ ).not.toThrow();
+ });
+
+ it('should accept multiple `metricTypes` in request query', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['ingest_rate', 'storage_retained', 'index_rate'],
+ })
+ ).not.toThrow();
+ });
+
+ it('should accept a single string as `dataStreams` in request query', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: 'storage_retained',
+ dataStreams: 'data_stream_1',
+ })
+ ).not.toThrow();
+ });
+
+ it('should accept `dataStream` list', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained'],
+ dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
+ })
+ ).not.toThrow();
+ });
+
+ it('should error if `dataStream` list is empty', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained'],
+ dataStreams: [],
+ })
+ ).toThrowError('expected value of type [string] but got [Array]');
+ });
+
+ it('should error if `dataStream` is given an empty string', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained'],
+ dataStreams: ' ',
+ })
+ ).toThrow('[dataStreams] must have at least one value');
+ });
+
+ it('should error if `dataStream` is given an empty item in the list', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained'],
+ dataStreams: ['ds_1', ' '],
+ })
+ ).toThrow('[dataStreams] list can not contain empty values');
+ });
+
+ it('should error if `metricTypes` is empty string', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ' ',
+ })
+ ).toThrow();
+ });
+
+ it('should error if `metricTypes` is empty item', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: [' ', 'storage_retained'],
+ })
+ ).toThrow('[metricTypes] list can not contain empty values');
+ });
+
+ it('should error if `metricTypes` is not a valid value', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: 'foo',
+ })
+ ).toThrow(
+ '[metricTypes] must be one of storage_retained, ingest_rate, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate'
+ );
+ });
+
+ it('should error if `metricTypes` is not a valid list', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained', 'foo'],
+ })
+ ).toThrow(
+ '[metricTypes] must be one of storage_retained, ingest_rate, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate'
+ );
+ });
+
+ it('should error if `from` is not a valid input', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: 1010,
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained', 'foo'],
+ })
+ ).toThrow('[from]: expected value of type [string] but got [number]');
+ });
+
+ it('should error if `to` is not a valid input', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: 1010,
+ metricTypes: ['storage_retained', 'foo'],
+ })
+ ).toThrow('[to]: expected value of type [string] but got [number]');
+ });
+
+ it('should error if `from` is empty string', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: ' ',
+ to: new Date().toISOString(),
+ metricTypes: ['storage_retained', 'foo'],
+ })
+ ).toThrow('[from]: Date ISO string must not be empty');
+ });
+
+ it('should error if `to` is empty string', () => {
+ expect(() =>
+ UsageMetricsRequestSchema.query.validate({
+ from: new Date().toISOString(),
+ to: ' ',
+ metricTypes: ['storage_retained', 'foo'],
+ })
+ ).toThrow('[to]: Date ISO string must not be empty');
+ });
+});
diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts
new file mode 100644
index 00000000000000..f2bbdb616fc798
--- /dev/null
+++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts
@@ -0,0 +1,102 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { schema, type TypeOf } from '@kbn/config-schema';
+
+const METRIC_TYPE_VALUES = [
+ 'storage_retained',
+ 'ingest_rate',
+ 'search_vcu',
+ 'ingest_vcu',
+ 'ml_vcu',
+ 'index_latency',
+ 'index_rate',
+ 'search_latency',
+ 'search_rate',
+] as const;
+
+export type MetricTypes = (typeof METRIC_TYPE_VALUES)[number];
+
+// type guard for MetricTypes
+export const isMetricType = (type: string): type is MetricTypes =>
+ METRIC_TYPE_VALUES.includes(type as MetricTypes);
+
+// @ts-ignore
+const isValidMetricType = (value: string) => METRIC_TYPE_VALUES.includes(value);
+
+const DateSchema = schema.string({
+ minLength: 1,
+ validate: (v) => (v.trim().length ? undefined : 'Date ISO string must not be empty'),
+});
+
+const metricTypesSchema = schema.oneOf(
+ // @ts-expect-error TS2769: No overload matches this call
+ METRIC_TYPE_VALUES.map((metricType) => schema.literal(metricType)) // Create a oneOf schema for the keys
+);
+export const UsageMetricsRequestSchema = {
+ query: schema.object({
+ from: DateSchema,
+ to: DateSchema,
+ metricTypes: schema.oneOf([
+ schema.arrayOf(schema.string(), {
+ minSize: 1,
+ validate: (values) => {
+ if (values.map((v) => v.trim()).some((v) => !v.length)) {
+ return '[metricTypes] list can not contain empty values';
+ } else if (values.map((v) => v.trim()).some((v) => !isValidMetricType(v))) {
+ return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`;
+ }
+ },
+ }),
+ schema.string({
+ validate: (v) => {
+ if (!v.trim().length) {
+ return '[metricTypes] must have at least one value';
+ } else if (!isValidMetricType(v)) {
+ return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`;
+ }
+ },
+ }),
+ ]),
+ dataStreams: schema.maybe(
+ schema.oneOf([
+ schema.arrayOf(schema.string(), {
+ minSize: 1,
+ validate: (values) => {
+ if (values.map((v) => v.trim()).some((v) => !v.length)) {
+ return '[dataStreams] list can not contain empty values';
+ }
+ },
+ }),
+ schema.string({
+ validate: (v) =>
+ v.trim().length ? undefined : '[dataStreams] must have at least one value',
+ }),
+ ])
+ ),
+ }),
+};
+
+export type UsageMetricsRequestSchemaQueryParams = TypeOf;
+
+export const UsageMetricsResponseSchema = {
+ body: () =>
+ schema.object({
+ metrics: schema.recordOf(
+ metricTypesSchema,
+ schema.arrayOf(
+ schema.object({
+ name: schema.string(),
+ data: schema.arrayOf(
+ schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 }) // Each data point is an array of 2 numbers
+ ),
+ })
+ )
+ ),
+ }),
+};
+export type UsageMetricsResponseSchemaBody = TypeOf;
diff --git a/x-pack/plugins/data_usage/common/types.ts b/x-pack/plugins/data_usage/common/types.ts
new file mode 100644
index 00000000000000..d80bae2458d09f
--- /dev/null
+++ b/x-pack/plugins/data_usage/common/types.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+// temporary type until agreed on
+export type MetricKey = 'ingestedMax' | 'retainedMax';
diff --git a/x-pack/plugins/data_usage/kibana.jsonc b/x-pack/plugins/data_usage/kibana.jsonc
index 9b0f2d193925e1..ffd8833351267a 100644
--- a/x-pack/plugins/data_usage/kibana.jsonc
+++ b/x-pack/plugins/data_usage/kibana.jsonc
@@ -1,16 +1,28 @@
{
"type": "plugin",
"id": "@kbn/data-usage-plugin",
- "owner": ["@elastic/obs-ai-assistant", "@elastic/security-solution"],
+ "owner": [
+ "@elastic/obs-ai-assistant",
+ "@elastic/security-solution"
+ ],
"plugin": {
"id": "dataUsage",
"server": true,
"browser": true,
- "configPath": ["xpack", "dataUsage"],
- "requiredPlugins": ["home", "management", "features", "share"],
+ "configPath": [
+ "xpack",
+ "dataUsage"
+ ],
+ "requiredPlugins": [
+ "home",
+ "management",
+ "features",
+ "share"
+ ],
"optionalPlugins": [],
"requiredBundles": [
"kibanaReact",
- ],
+ "data"
+ ]
}
}
diff --git a/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx b/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx
new file mode 100644
index 00000000000000..c7937ae149de98
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx
@@ -0,0 +1,123 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { useCallback, useMemo } from 'react';
+import numeral from '@elastic/numeral';
+import { EuiFlexItem, EuiPanel, EuiTitle, useEuiTheme } from '@elastic/eui';
+import {
+ Chart,
+ Axis,
+ BarSeries,
+ Settings,
+ ScaleType,
+ niceTimeFormatter,
+ DARK_THEME,
+ LIGHT_THEME,
+} from '@elastic/charts';
+import { i18n } from '@kbn/i18n';
+import { LegendAction } from './legend_action';
+import { MetricTypes } from '../../../common/rest_types';
+import { MetricSeries } from '../types';
+
+// TODO: Remove this when we have a title for each metric type
+type ChartKey = Extract;
+export const chartKeyToTitleMap: Record = {
+ ingest_rate: i18n.translate('xpack.dataUsage.charts.ingestedMax', {
+ defaultMessage: 'Data Ingested',
+ }),
+ storage_retained: i18n.translate('xpack.dataUsage.charts.retainedMax', {
+ defaultMessage: 'Data Retained in Storage',
+ }),
+};
+
+interface ChartPanelProps {
+ metricType: MetricTypes;
+ series: MetricSeries[];
+ idx: number;
+ popoverOpen: string | null;
+ togglePopover: (streamName: string | null) => void;
+}
+
+export const ChartPanel: React.FC = ({
+ metricType,
+ series,
+ idx,
+ popoverOpen,
+ togglePopover,
+}) => {
+ const theme = useEuiTheme();
+
+ const chartTimestamps = series.flatMap((stream) => stream.data.map((d) => d[0]));
+
+ const [minTimestamp, maxTimestamp] = [Math.min(...chartTimestamps), Math.max(...chartTimestamps)];
+
+ const tickFormat = useMemo(
+ () => niceTimeFormatter([minTimestamp, maxTimestamp]),
+ [minTimestamp, maxTimestamp]
+ );
+
+ const renderLegendAction = useCallback(
+ ({ label }: { label: string }) => {
+ return (
+
+ );
+ },
+ [idx, popoverOpen, togglePopover]
+ );
+ return (
+
+
+
+ {chartKeyToTitleMap[metricType as ChartKey] || metricType}
+
+
+
+ {series.map((stream, streamIdx) => (
+
+ ))}
+
+
+
+ formatBytes(d)}
+ />
+
+
+
+ );
+};
+const formatBytes = (bytes: number) => {
+ return numeral(bytes).format('0.0 b');
+};
diff --git a/x-pack/plugins/data_usage/public/app/components/charts.tsx b/x-pack/plugins/data_usage/public/app/components/charts.tsx
new file mode 100644
index 00000000000000..6549f7e03830ae
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/components/charts.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { useCallback, useState } from 'react';
+import { EuiFlexGroup } from '@elastic/eui';
+import { MetricsResponse } from '../types';
+import { MetricTypes } from '../../../common/rest_types';
+import { ChartPanel } from './chart_panel';
+interface ChartsProps {
+ data: MetricsResponse;
+}
+
+export const Charts: React.FC = ({ data }) => {
+ const [popoverOpen, setPopoverOpen] = useState(null);
+ const togglePopover = useCallback((streamName: string | null) => {
+ setPopoverOpen((prev) => (prev === streamName ? null : streamName));
+ }, []);
+
+ return (
+
+ {Object.entries(data.metrics).map(([metricType, series], idx) => (
+
+ ))}
+
+ );
+};
diff --git a/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx b/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx
new file mode 100644
index 00000000000000..d6627f3d8dca27
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. 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 { EuiListGroupItem } from '@elastic/eui';
+import {
+ DataQualityDetailsLocatorParams,
+ DATA_QUALITY_DETAILS_LOCATOR_ID,
+} from '@kbn/deeplinks-observability';
+import { useKibanaContextForPlugin } from '../../utils/use_kibana';
+import { useDateRangePicker } from '../hooks/use_date_picker';
+
+interface DatasetQualityLinkProps {
+ dataStreamName: string;
+}
+
+export const DatasetQualityLink: React.FC = React.memo(
+ ({ dataStreamName }) => {
+ const { dateRangePickerState } = useDateRangePicker();
+ const {
+ services: {
+ share: { url },
+ },
+ } = useKibanaContextForPlugin();
+ const { startDate, endDate } = dateRangePickerState;
+ const locator = url.locators.get(
+ DATA_QUALITY_DETAILS_LOCATOR_ID
+ );
+ const onClickDataQuality = async () => {
+ const locatorParams: DataQualityDetailsLocatorParams = {
+ dataStream: dataStreamName,
+ timeRange: { from: startDate, to: endDate, refresh: { pause: true, value: 0 } },
+ };
+ if (locator) {
+ await locator.navigate(locatorParams);
+ }
+ };
+ return ;
+ }
+);
diff --git a/x-pack/plugins/data_usage/public/app/components/date_picker.tsx b/x-pack/plugins/data_usage/public/app/components/date_picker.tsx
new file mode 100644
index 00000000000000..ca29acf8c96a6b
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/components/date_picker.tsx
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { memo, useState } from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, useEuiTheme } from '@elastic/eui';
+import { css } from '@emotion/react';
+import type { IUnifiedSearchPluginServices } from '@kbn/unified-search-plugin/public';
+import type { EuiSuperDatePickerRecentRange } from '@elastic/eui';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import type {
+ DurationRange,
+ OnRefreshChangeProps,
+} from '@elastic/eui/src/components/date_picker/types';
+import { UI_SETTINGS } from '@kbn/data-plugin/common';
+
+export interface DateRangePickerValues {
+ autoRefreshOptions: {
+ enabled: boolean;
+ duration: number;
+ };
+ startDate: string;
+ endDate: string;
+ recentlyUsedDateRanges: EuiSuperDatePickerRecentRange[];
+}
+
+interface UsageMetricsDateRangePickerProps {
+ dateRangePickerState: DateRangePickerValues;
+ isDataLoading: boolean;
+ onRefresh: () => void;
+ onRefreshChange: (evt: OnRefreshChangeProps) => void;
+ onTimeChange: ({ start, end }: DurationRange) => void;
+}
+
+export const UsageMetricsDateRangePicker = memo(
+ ({ dateRangePickerState, isDataLoading, onRefresh, onRefreshChange, onTimeChange }) => {
+ const { euiTheme } = useEuiTheme();
+ const kibana = useKibana();
+ const { uiSettings } = kibana.services;
+ const [commonlyUsedRanges] = useState(() => {
+ return (
+ uiSettings
+ ?.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES)
+ ?.map(({ from, to, display }: { from: string; to: string; display: string }) => {
+ return {
+ start: from,
+ end: to,
+ label: display,
+ };
+ }) ?? []
+ );
+ });
+
+ return (
+
+
+
+
+
+
+
+ );
+ }
+);
+
+UsageMetricsDateRangePicker.displayName = 'UsageMetricsDateRangePicker';
diff --git a/x-pack/plugins/data_usage/public/app/components/legend_action.tsx b/x-pack/plugins/data_usage/public/app/components/legend_action.tsx
new file mode 100644
index 00000000000000..a816d1f8eaddab
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/components/legend_action.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { useCallback } from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonIcon,
+ EuiPopover,
+ EuiListGroup,
+ EuiListGroupItem,
+ EuiSpacer,
+} from '@elastic/eui';
+import { DatasetQualityLink } from './dataset_quality_link';
+import { useKibanaContextForPlugin } from '../../utils/use_kibana';
+
+interface LegendActionProps {
+ idx: number;
+ popoverOpen: string | null;
+ togglePopover: (streamName: string | null) => void;
+ label: string;
+}
+
+export const LegendAction: React.FC = React.memo(
+ ({ label, idx, popoverOpen, togglePopover }) => {
+ const uniqueStreamName = `${idx}-${label}`;
+ const {
+ services: {
+ share: {
+ url: { locators },
+ },
+ application: { capabilities },
+ },
+ } = useKibanaContextForPlugin();
+ const hasDataSetQualityFeature = !!capabilities?.data_quality;
+ const hasIndexManagementFeature = !!capabilities?.index_management;
+
+ const onClickIndexManagement = useCallback(async () => {
+ // TODO: use proper index management locator https://github.com/elastic/kibana/issues/195083
+ const dataQualityLocator = locators.get('MANAGEMENT_APP_LOCATOR');
+ if (dataQualityLocator) {
+ await dataQualityLocator.navigate({
+ sectionId: 'data',
+ appId: `index_management/data_streams/${label}`,
+ });
+ }
+ togglePopover(null); // Close the popover after action
+ }, [label, locators, togglePopover]);
+
+ const onCopyDataStreamName = useCallback(() => {
+ navigator.clipboard.writeText(label);
+ togglePopover(null); // Close popover after copying
+ }, [label, togglePopover]);
+
+ return (
+
+
+
+ togglePopover(uniqueStreamName)}
+ />
+
+
+ }
+ isOpen={popoverOpen === uniqueStreamName}
+ closePopover={() => togglePopover(null)}
+ anchorPosition="downRight"
+ >
+
+
+
+
+ {hasIndexManagementFeature && (
+
+ )}
+ {hasDataSetQualityFeature && }
+
+
+
+ );
+ }
+);
diff --git a/x-pack/plugins/data_usage/public/app/data_usage.tsx b/x-pack/plugins/data_usage/public/app/data_usage.tsx
new file mode 100644
index 00000000000000..c32f86d68b5bf8
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/data_usage.tsx
@@ -0,0 +1,147 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+ EuiTitle,
+ EuiSpacer,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingElastic,
+ EuiPageSection,
+ EuiText,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { i18n } from '@kbn/i18n';
+import { UsageMetricsRequestSchemaQueryParams } from '../../common/rest_types';
+import { Charts } from './components/charts';
+import { UsageMetricsDateRangePicker } from './components/date_picker';
+import { useBreadcrumbs } from '../utils/use_breadcrumbs';
+import { useKibanaContextForPlugin } from '../utils/use_kibana';
+import { PLUGIN_NAME } from '../../common';
+import { useGetDataUsageMetrics } from '../hooks/use_get_usage_metrics';
+import { DEFAULT_DATE_RANGE_OPTIONS, useDateRangePicker } from './hooks/use_date_picker';
+import { useDataUsageMetricsUrlParams } from './hooks/use_charts_url_params';
+import { MetricsResponse } from './types';
+
+export const DataUsage = () => {
+ const {
+ services: { chrome, appParams },
+ } = useKibanaContextForPlugin();
+
+ const {
+ metricTypes: metricTypesFromUrl,
+ dataStreams: dataStreamsFromUrl,
+ startDate: startDateFromUrl,
+ endDate: endDateFromUrl,
+ setUrlMetricTypesFilter,
+ setUrlDateRangeFilter,
+ } = useDataUsageMetricsUrlParams();
+
+ const [queryParams, setQueryParams] = useState({
+ metricTypes: ['storage_retained', 'ingest_rate'],
+ dataStreams: [],
+ from: DEFAULT_DATE_RANGE_OPTIONS.startDate,
+ to: DEFAULT_DATE_RANGE_OPTIONS.endDate,
+ });
+
+ useEffect(() => {
+ if (!metricTypesFromUrl) {
+ setUrlMetricTypesFilter(
+ typeof queryParams.metricTypes !== 'string'
+ ? queryParams.metricTypes.join(',')
+ : queryParams.metricTypes
+ );
+ }
+ if (!startDateFromUrl || !endDateFromUrl) {
+ setUrlDateRangeFilter({ startDate: queryParams.from, endDate: queryParams.to });
+ }
+ }, [
+ endDateFromUrl,
+ metricTypesFromUrl,
+ queryParams.from,
+ queryParams.metricTypes,
+ queryParams.to,
+ setUrlDateRangeFilter,
+ setUrlMetricTypesFilter,
+ startDateFromUrl,
+ ]);
+
+ useEffect(() => {
+ setQueryParams((prevState) => ({
+ ...prevState,
+ metricTypes: metricTypesFromUrl?.length ? metricTypesFromUrl : prevState.metricTypes,
+ dataStreams: dataStreamsFromUrl?.length ? dataStreamsFromUrl : prevState.dataStreams,
+ }));
+ }, [metricTypesFromUrl, dataStreamsFromUrl]);
+
+ const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker();
+
+ const {
+ error,
+ data,
+ isFetching,
+ isFetched,
+ refetch: refetchDataUsageMetrics,
+ } = useGetDataUsageMetrics(
+ {
+ ...queryParams,
+ from: dateRangePickerState.startDate,
+ to: dateRangePickerState.endDate,
+ },
+ {
+ retry: false,
+ }
+ );
+
+ const onRefresh = useCallback(() => {
+ refetchDataUsageMetrics();
+ }, [refetchDataUsageMetrics]);
+
+ useBreadcrumbs([{ text: PLUGIN_NAME }], appParams, chrome);
+
+ // TODO: show a toast?
+ if (!isFetching && error?.body) {
+ return {error.body.message}
;
+ }
+
+ return (
+ <>
+
+
+ {i18n.translate('xpack.dataUsage.pageTitle', {
+ defaultMessage: 'Data Usage',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isFetched && data ? : }
+
+ >
+ );
+};
diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx
new file mode 100644
index 00000000000000..0e03da5d9adbd3
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx
@@ -0,0 +1,136 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useHistory, useLocation } from 'react-router-dom';
+import { MetricTypes, isMetricType } from '../../../common/rest_types';
+import { useUrlParams } from '../../hooks/use_url_params';
+import { DEFAULT_DATE_RANGE_OPTIONS } from './use_date_picker';
+
+interface UrlParamsDataUsageMetricsFilters {
+ metricTypes: string;
+ dataStreams: string;
+ startDate: string;
+ endDate: string;
+}
+
+interface DataUsageMetricsFiltersFromUrlParams {
+ metricTypes?: MetricTypes[];
+ dataStreams?: string[];
+ startDate?: string;
+ endDate?: string;
+ setUrlDataStreamsFilter: (dataStreams: UrlParamsDataUsageMetricsFilters['dataStreams']) => void;
+ setUrlDateRangeFilter: ({ startDate, endDate }: { startDate: string; endDate: string }) => void;
+ setUrlMetricTypesFilter: (metricTypes: UrlParamsDataUsageMetricsFilters['metricTypes']) => void;
+}
+
+type FiltersFromUrl = Pick<
+ DataUsageMetricsFiltersFromUrlParams,
+ 'metricTypes' | 'dataStreams' | 'startDate' | 'endDate'
+>;
+
+export const getDataUsageMetricsFiltersFromUrlParams = (
+ urlParams: Partial
+): FiltersFromUrl => {
+ const dataUsageMetricsFilters: FiltersFromUrl = {
+ metricTypes: [],
+ dataStreams: [],
+ startDate: DEFAULT_DATE_RANGE_OPTIONS.startDate,
+ endDate: DEFAULT_DATE_RANGE_OPTIONS.endDate,
+ };
+
+ const urlMetricTypes = urlParams.metricTypes
+ ? String(urlParams.metricTypes)
+ .split(',')
+ .reduce((acc, curr) => {
+ if (isMetricType(curr)) {
+ acc.push(curr);
+ }
+ return acc.sort();
+ }, [])
+ : [];
+
+ const urlDataStreams = urlParams.dataStreams
+ ? String(urlParams.dataStreams).split(',').sort()
+ : [];
+
+ dataUsageMetricsFilters.metricTypes = urlMetricTypes.length ? urlMetricTypes : undefined;
+ dataUsageMetricsFilters.dataStreams = urlDataStreams.length ? urlDataStreams : undefined;
+ dataUsageMetricsFilters.startDate = urlParams.startDate ? String(urlParams.startDate) : undefined;
+ dataUsageMetricsFilters.endDate = urlParams.endDate ? String(urlParams.endDate) : undefined;
+
+ return dataUsageMetricsFilters;
+};
+
+export const useDataUsageMetricsUrlParams = (): DataUsageMetricsFiltersFromUrlParams => {
+ const location = useLocation();
+ const history = useHistory();
+ const { urlParams, toUrlParams } = useUrlParams();
+
+ const getUrlDataUsageMetricsFilters: FiltersFromUrl = useMemo(
+ () => getDataUsageMetricsFiltersFromUrlParams(urlParams),
+ [urlParams]
+ );
+ const [dataUsageMetricsFilters, setDataUsageMetricsFilters] = useState(
+ getUrlDataUsageMetricsFilters
+ );
+
+ const setUrlMetricTypesFilter = useCallback(
+ (metricTypes: string) => {
+ history.push({
+ ...location,
+ search: toUrlParams({
+ ...urlParams,
+ metricTypes: metricTypes.length ? metricTypes : undefined,
+ }),
+ });
+ },
+ [history, location, toUrlParams, urlParams]
+ );
+
+ const setUrlDataStreamsFilter = useCallback(
+ (dataStreams: string) => {
+ history.push({
+ ...location,
+ search: toUrlParams({
+ ...urlParams,
+ dataStreams: dataStreams.length ? dataStreams : undefined,
+ }),
+ });
+ },
+ [history, location, toUrlParams, urlParams]
+ );
+
+ const setUrlDateRangeFilter = useCallback(
+ ({ startDate, endDate }: { startDate: string; endDate: string }) => {
+ history.push({
+ ...location,
+ search: toUrlParams({
+ ...urlParams,
+ startDate: startDate.length ? startDate : undefined,
+ endDate: endDate.length ? endDate : undefined,
+ }),
+ });
+ },
+ [history, location, toUrlParams, urlParams]
+ );
+
+ useEffect(() => {
+ setDataUsageMetricsFilters((prevState) => {
+ return {
+ ...prevState,
+ ...getDataUsageMetricsFiltersFromUrlParams(urlParams),
+ };
+ });
+ }, [setDataUsageMetricsFilters, urlParams]);
+
+ return {
+ ...dataUsageMetricsFilters,
+ setUrlDataStreamsFilter,
+ setUrlDateRangeFilter,
+ setUrlMetricTypesFilter,
+ };
+};
diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx
new file mode 100644
index 00000000000000..b5407ae9e46d59
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx
@@ -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 { useCallback, useState } from 'react';
+import type {
+ DurationRange,
+ OnRefreshChangeProps,
+} from '@elastic/eui/src/components/date_picker/types';
+import { useDataUsageMetricsUrlParams } from './use_charts_url_params';
+import { DateRangePickerValues } from '../components/date_picker';
+
+export const DEFAULT_DATE_RANGE_OPTIONS = Object.freeze({
+ autoRefreshOptions: {
+ enabled: false,
+ duration: 10000,
+ },
+ startDate: 'now-24h/h',
+ endDate: 'now',
+ recentlyUsedDateRanges: [],
+});
+
+export const useDateRangePicker = () => {
+ const {
+ setUrlDateRangeFilter,
+ startDate: startDateFromUrl,
+ endDate: endDateFromUrl,
+ } = useDataUsageMetricsUrlParams();
+ const [dateRangePickerState, setDateRangePickerState] = useState({
+ ...DEFAULT_DATE_RANGE_OPTIONS,
+ startDate: startDateFromUrl ?? DEFAULT_DATE_RANGE_OPTIONS.startDate,
+ endDate: endDateFromUrl ?? DEFAULT_DATE_RANGE_OPTIONS.endDate,
+ });
+
+ const updateUsageMetricsDateRanges = useCallback(
+ ({ start, end }: DurationRange) => {
+ setDateRangePickerState((prevState) => ({
+ ...prevState,
+ startDate: start,
+ endDate: end,
+ }));
+ },
+ [setDateRangePickerState]
+ );
+
+ const updateUsageMetricsRecentlyUsedDateRanges = useCallback(
+ (recentlyUsedDateRanges: DateRangePickerValues['recentlyUsedDateRanges']) => {
+ setDateRangePickerState((prevState) => ({
+ ...prevState,
+ recentlyUsedDateRanges,
+ }));
+ },
+ [setDateRangePickerState]
+ );
+
+ // handle refresh timer update
+ const onRefreshChange = useCallback(
+ (evt: OnRefreshChangeProps) => {
+ setDateRangePickerState((prevState) => ({
+ ...prevState,
+ autoRefreshOptions: { enabled: !evt.isPaused, duration: evt.refreshInterval },
+ }));
+ },
+ [setDateRangePickerState]
+ );
+
+ // handle manual time change on date picker
+ const onTimeChange = useCallback(
+ ({ start: newStart, end: newEnd }: DurationRange) => {
+ // update date ranges
+ updateUsageMetricsDateRanges({ start: newStart, end: newEnd });
+
+ // update recently used date ranges
+ const newRecentlyUsedDateRanges = [
+ { start: newStart, end: newEnd },
+ ...dateRangePickerState.recentlyUsedDateRanges
+ .filter(
+ (recentlyUsedRange: DurationRange) =>
+ !(recentlyUsedRange.start === newStart && recentlyUsedRange.end === newEnd)
+ )
+ .slice(0, 9),
+ ];
+ updateUsageMetricsRecentlyUsedDateRanges(newRecentlyUsedDateRanges);
+ setUrlDateRangeFilter({ startDate: newStart, endDate: newEnd });
+ },
+ [
+ dateRangePickerState.recentlyUsedDateRanges,
+ setUrlDateRangeFilter,
+ updateUsageMetricsDateRanges,
+ updateUsageMetricsRecentlyUsedDateRanges,
+ ]
+ );
+
+ return { dateRangePickerState, onRefreshChange, onTimeChange };
+};
diff --git a/x-pack/plugins/data_usage/public/app/types.ts b/x-pack/plugins/data_usage/public/app/types.ts
new file mode 100644
index 00000000000000..13f53bc2ea6dd8
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/app/types.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { MetricTypes } from '../../common/rest_types';
+
+export type DataPoint = [number, number]; // [timestamp, value]
+
+export interface MetricSeries {
+ name: string; // Name of the data stream
+ data: DataPoint[]; // Array of data points in tuple format [timestamp, value]
+}
+// Use MetricTypes dynamically as keys for the Metrics interface
+export type Metrics = Partial>;
+
+export interface MetricsResponse {
+ metrics: Metrics;
+}
+export interface MetricsResponse {
+ metrics: Metrics;
+}
diff --git a/x-pack/plugins/data_usage/public/application.tsx b/x-pack/plugins/data_usage/public/application.tsx
index 1e6c35c4b8f0a7..054aae397e5e15 100644
--- a/x-pack/plugins/data_usage/public/application.tsx
+++ b/x-pack/plugins/data_usage/public/application.tsx
@@ -16,6 +16,8 @@ import { PerformanceContextProvider } from '@kbn/ebt-tools';
import { useKibanaContextForPluginProvider } from './utils/use_kibana';
import { DataUsageStartDependencies, DataUsagePublicStart } from './types';
import { PLUGIN_ID } from '../common';
+import { DataUsage } from './app/data_usage';
+import { DataUsageReactQueryClientProvider } from '../common/query_client';
export const renderApp = (
core: CoreStart,
@@ -51,7 +53,7 @@ const AppWithExecutionContext = ({
- Data Usage
} />
+
@@ -76,7 +78,9 @@ const App = ({ core, plugins, pluginStart, params }: AppProps) => {
return (
-
+
+
+
);
diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts
new file mode 100644
index 00000000000000..6b9860e997c125
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
+import { useQuery } from '@tanstack/react-query';
+import type { IHttpFetchError } from '@kbn/core-http-browser';
+import {
+ UsageMetricsRequestSchemaQueryParams,
+ UsageMetricsResponseSchemaBody,
+} from '../../common/rest_types';
+import { DATA_USAGE_METRICS_API_ROUTE } from '../../common';
+import { useKibanaContextForPlugin } from '../utils/use_kibana';
+
+interface ErrorType {
+ statusCode: number;
+ message: string;
+}
+
+export const useGetDataUsageMetrics = (
+ query: UsageMetricsRequestSchemaQueryParams,
+ options: UseQueryOptions> = {}
+): UseQueryResult> => {
+ const http = useKibanaContextForPlugin().services.http;
+
+ return useQuery>({
+ queryKey: ['get-data-usage-metrics', query],
+ ...options,
+ keepPreviousData: true,
+ queryFn: async () => {
+ return http.get(DATA_USAGE_METRICS_API_ROUTE, {
+ version: '1',
+ query: {
+ from: query.from,
+ to: query.to,
+ metricTypes: query.metricTypes,
+ dataStreams: query.dataStreams,
+ },
+ });
+ },
+ });
+};
diff --git a/x-pack/plugins/data_usage/public/hooks/use_url_params.ts b/x-pack/plugins/data_usage/public/hooks/use_url_params.ts
new file mode 100644
index 00000000000000..865b71781df63f
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/hooks/use_url_params.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useMemo } from 'react';
+import { parse, stringify } from 'query-string';
+import { useLocation } from 'react-router-dom';
+
+/**
+ * Parses `search` params and returns an object with them along with a `toUrlParams` function
+ * that allows being able to retrieve a stringified version of an object (default is the
+ * `urlParams` that was parsed) for use in the url.
+ * Object will be recreated every time `search` changes.
+ */
+export function useUrlParams>(): {
+ urlParams: T;
+ toUrlParams: (params?: T) => string;
+} {
+ const { search } = useLocation();
+ return useMemo(() => {
+ const urlParams = parse(search) as unknown as T;
+ return {
+ urlParams,
+ toUrlParams: (params: T = urlParams) => stringify(params as unknown as object),
+ };
+ }, [search]);
+}
diff --git a/x-pack/plugins/data_usage/public/utils/use_breadcrumbs.tsx b/x-pack/plugins/data_usage/public/utils/use_breadcrumbs.tsx
new file mode 100644
index 00000000000000..928ee73ad52806
--- /dev/null
+++ b/x-pack/plugins/data_usage/public/utils/use_breadcrumbs.tsx
@@ -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 type { ChromeBreadcrumb, ChromeStart } from '@kbn/core-chrome-browser';
+
+import { useEffect } from 'react';
+import { ManagementAppMountParams } from '@kbn/management-plugin/public';
+
+export const useBreadcrumbs = (
+ breadcrumbs: ChromeBreadcrumb[],
+ params: ManagementAppMountParams,
+ chromeService: ChromeStart
+) => {
+ const { docTitle } = chromeService;
+ const isMultiple = breadcrumbs.length > 1;
+
+ const docTitleValue = isMultiple ? breadcrumbs[breadcrumbs.length - 1].text : breadcrumbs[0].text;
+
+ docTitle.change(docTitleValue as string);
+
+ useEffect(() => {
+ params.setBreadcrumbs(breadcrumbs);
+ }, [breadcrumbs, params]);
+};
diff --git a/x-pack/plugins/data_usage/server/common/errors.ts b/x-pack/plugins/data_usage/server/common/errors.ts
new file mode 100644
index 00000000000000..7a43a10108be16
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/common/errors.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export class BaseError extends Error {
+ constructor(message: string, public readonly meta?: MetaType) {
+ super(message);
+ // For debugging - capture name of subclasses
+ this.name = this.constructor.name;
+
+ if (meta instanceof Error) {
+ this.stack += `\n----- original error -----\n${meta.stack}`;
+ }
+ }
+}
diff --git a/x-pack/plugins/data_usage/server/config.ts b/x-pack/plugins/data_usage/server/config.ts
index 6453cce4f4d564..bf89431f2abea4 100644
--- a/x-pack/plugins/data_usage/server/config.ts
+++ b/x-pack/plugins/data_usage/server/config.ts
@@ -6,9 +6,18 @@
*/
import { schema, type TypeOf } from '@kbn/config-schema';
+import { PluginInitializerContext } from '@kbn/core/server';
-export const config = schema.object({
+export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
});
-export type DataUsageConfig = TypeOf;
+export type DataUsageConfigType = TypeOf;
+
+export const createConfig = (context: PluginInitializerContext): DataUsageConfigType => {
+ const pluginConfig = context.config.get>();
+
+ return {
+ ...pluginConfig,
+ };
+};
diff --git a/x-pack/plugins/data_usage/server/index.ts b/x-pack/plugins/data_usage/server/index.ts
index 3aa49a184d0039..66d839303d7165 100644
--- a/x-pack/plugins/data_usage/server/index.ts
+++ b/x-pack/plugins/data_usage/server/index.ts
@@ -9,7 +9,7 @@ import type {
PluginInitializerContext,
PluginConfigDescriptor,
} from '@kbn/core/server';
-import { DataUsageConfig } from './config';
+import { DataUsageConfigType } from './config';
import { DataUsagePlugin } from './plugin';
import type {
@@ -19,11 +19,11 @@ import type {
DataUsageStartDependencies,
} from './types';
-import { config as configSchema } from './config';
+import { configSchema } from './config';
export type { DataUsageServerSetup, DataUsageServerStart };
-export const config: PluginConfigDescriptor = {
+export const config: PluginConfigDescriptor = {
schema: configSchema,
};
@@ -32,5 +32,5 @@ export const plugin: PluginInitializer<
DataUsageServerStart,
DataUsageSetupDependencies,
DataUsageStartDependencies
-> = async (pluginInitializerContext: PluginInitializerContext) =>
+> = async (pluginInitializerContext: PluginInitializerContext) =>
await new DataUsagePlugin(pluginInitializerContext);
diff --git a/x-pack/plugins/data_usage/server/plugin.ts b/x-pack/plugins/data_usage/server/plugin.ts
index 8ab49d5104fffe..2beae9b22bba97 100644
--- a/x-pack/plugins/data_usage/server/plugin.ts
+++ b/x-pack/plugins/data_usage/server/plugin.ts
@@ -7,13 +7,17 @@
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
-import { DataUsageConfig } from './config';
+import { DataUsageConfigType, createConfig } from './config';
import type {
+ DataUsageContext,
+ DataUsageRequestHandlerContext,
DataUsageServerSetup,
DataUsageServerStart,
DataUsageSetupDependencies,
DataUsageStartDependencies,
} from './types';
+import { registerDataUsageRoutes } from './routes';
+import { PLUGIN_ID } from '../common';
export class DataUsagePlugin
implements
@@ -24,11 +28,39 @@ export class DataUsagePlugin
DataUsageStartDependencies
>
{
- logger: Logger;
- constructor(context: PluginInitializerContext) {
+ private readonly logger: Logger;
+ private dataUsageContext: DataUsageContext;
+
+ constructor(context: PluginInitializerContext) {
+ const serverConfig = createConfig(context);
+
this.logger = context.logger.get();
+
+ this.logger.debug('data usage plugin initialized');
+ this.dataUsageContext = {
+ logFactory: context.logger,
+ get serverConfig() {
+ return serverConfig;
+ },
+ };
}
setup(coreSetup: CoreSetup, pluginsSetup: DataUsageSetupDependencies): DataUsageServerSetup {
+ this.logger.debug('data usage plugin setup');
+ pluginsSetup.features.registerElasticsearchFeature({
+ id: PLUGIN_ID,
+ management: {
+ data: [PLUGIN_ID],
+ },
+ privileges: [
+ {
+ requiredClusterPrivileges: ['monitor'],
+ ui: [],
+ },
+ ],
+ });
+ const router = coreSetup.http.createRouter();
+ registerDataUsageRoutes(router, this.dataUsageContext);
+
return {};
}
@@ -36,5 +68,7 @@ export class DataUsagePlugin
return {};
}
- public stop() {}
+ public stop() {
+ this.logger.debug('Stopping data usage plugin');
+ }
}
diff --git a/x-pack/plugins/data_usage/server/routes/error_handler.ts b/x-pack/plugins/data_usage/server/routes/error_handler.ts
new file mode 100644
index 00000000000000..122df5e72b130b
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/error_handler.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server';
+import { CustomHttpRequestError } from '../utils/custom_http_request_error';
+import { BaseError } from '../common/errors';
+
+export class NotFoundError extends BaseError {}
+
+/**
+ * Default Data Usage Routes error handler
+ * @param logger
+ * @param res
+ * @param error
+ */
+export const errorHandler = (
+ logger: Logger,
+ res: KibanaResponseFactory,
+ error: E
+): IKibanaResponse => {
+ logger.error(error);
+
+ if (error instanceof CustomHttpRequestError) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ if (error instanceof NotFoundError) {
+ return res.notFound({ body: error });
+ }
+
+ // Kibana CORE will take care of `500` errors when the handler `throw`'s, including logging the error
+ throw error;
+};
diff --git a/x-pack/plugins/data_usage/server/routes/index.tsx b/x-pack/plugins/data_usage/server/routes/index.tsx
new file mode 100644
index 00000000000000..b6b80c38864f33
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/index.tsx
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { DataUsageContext, DataUsageRouter } from '../types';
+import { registerDataStreamsRoute, registerUsageMetricsRoute } from './internal';
+
+export const registerDataUsageRoutes = (
+ router: DataUsageRouter,
+ dataUsageContext: DataUsageContext
+) => {
+ registerUsageMetricsRoute(router, dataUsageContext);
+ registerDataStreamsRoute(router, dataUsageContext);
+};
diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams.ts
new file mode 100644
index 00000000000000..0d71d93b55849e
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams.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 { DataStreamsResponseSchema } from '../../../common/rest_types';
+import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../../common';
+import { DataUsageContext, DataUsageRouter } from '../../types';
+
+import { getDataStreamsHandler } from './data_streams_handler';
+
+export const registerDataStreamsRoute = (
+ router: DataUsageRouter,
+ dataUsageContext: DataUsageContext
+) => {
+ if (dataUsageContext.serverConfig.enabled) {
+ router.versioned
+ .get({
+ access: 'internal',
+ path: DATA_USAGE_DATA_STREAMS_API_ROUTE,
+ })
+ .addVersion(
+ {
+ version: '1',
+ validate: {
+ request: {},
+ response: {
+ 200: DataStreamsResponseSchema,
+ },
+ },
+ },
+ getDataStreamsHandler(dataUsageContext)
+ );
+ }
+};
diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts
new file mode 100644
index 00000000000000..686edd0c4f4b7e
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { RequestHandler } from '@kbn/core/server';
+import { DataUsageContext, DataUsageRequestHandlerContext } from '../../types';
+
+import { errorHandler } from '../error_handler';
+
+export const getDataStreamsHandler = (
+ dataUsageContext: DataUsageContext
+): RequestHandler => {
+ const logger = dataUsageContext.logFactory.get('dataStreamsRoute');
+
+ return async (context, _, response) => {
+ logger.debug(`Retrieving user data streams`);
+
+ try {
+ const core = await context.core;
+ const esClient = core.elasticsearch.client.asCurrentUser;
+
+ const { data_streams: dataStreamsResponse } = await esClient.indices.dataStreamsStats({
+ name: '*',
+ expand_wildcards: 'all',
+ });
+
+ const sorted = dataStreamsResponse
+ .sort((a, b) => b.store_size_bytes - a.store_size_bytes)
+ .map((dataStream) => ({
+ name: dataStream.data_stream,
+ storageSizeBytes: dataStream.store_size_bytes,
+ }));
+ return response.ok({
+ body: sorted,
+ });
+ } catch (error) {
+ return errorHandler(logger, response, error);
+ }
+ };
+};
diff --git a/x-pack/plugins/data_usage/server/routes/internal/index.tsx b/x-pack/plugins/data_usage/server/routes/internal/index.tsx
new file mode 100644
index 00000000000000..e8d874bb7e6af9
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/internal/index.tsx
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { registerUsageMetricsRoute } from './usage_metrics';
+export { registerDataStreamsRoute } from './data_streams';
diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts
new file mode 100644
index 00000000000000..5bf3008ef668a9
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.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 { UsageMetricsRequestSchema, UsageMetricsResponseSchema } from '../../../common/rest_types';
+import { DATA_USAGE_METRICS_API_ROUTE } from '../../../common';
+import { DataUsageContext, DataUsageRouter } from '../../types';
+
+import { getUsageMetricsHandler } from './usage_metrics_handler';
+
+export const registerUsageMetricsRoute = (
+ router: DataUsageRouter,
+ dataUsageContext: DataUsageContext
+) => {
+ if (dataUsageContext.serverConfig.enabled) {
+ router.versioned
+ .get({
+ access: 'internal',
+ path: DATA_USAGE_METRICS_API_ROUTE,
+ })
+ .addVersion(
+ {
+ version: '1',
+ validate: {
+ request: UsageMetricsRequestSchema,
+ response: {
+ 200: UsageMetricsResponseSchema,
+ },
+ },
+ },
+ getUsageMetricsHandler(dataUsageContext)
+ );
+ }
+};
diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts
new file mode 100644
index 00000000000000..6f992c9fb2a383
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts
@@ -0,0 +1,237 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { RequestHandler } from '@kbn/core/server';
+import { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types';
+import {
+ MetricTypes,
+ UsageMetricsRequestSchemaQueryParams,
+ UsageMetricsResponseSchema,
+} from '../../../common/rest_types';
+import { DataUsageContext, DataUsageRequestHandlerContext } from '../../types';
+
+import { errorHandler } from '../error_handler';
+
+const formatStringParams = (value: T | T[]): T[] | MetricTypes[] =>
+ typeof value === 'string' ? [value] : value;
+
+export const getUsageMetricsHandler = (
+ dataUsageContext: DataUsageContext
+): RequestHandler<
+ never,
+ UsageMetricsRequestSchemaQueryParams,
+ unknown,
+ DataUsageRequestHandlerContext
+> => {
+ const logger = dataUsageContext.logFactory.get('usageMetricsRoute');
+
+ return async (context, request, response) => {
+ try {
+ const core = await context.core;
+ const esClient = core.elasticsearch.client.asCurrentUser;
+
+ // @ts-ignore
+ const { from, to, metricTypes, dataStreams: dsNames, size } = request.query;
+ logger.debug(`Retrieving usage metrics`);
+
+ const { data_streams: dataStreamsResponse }: IndicesGetDataStreamResponse =
+ await esClient.indices.getDataStream({
+ name: '*',
+ expand_wildcards: 'all',
+ });
+
+ const hasDataStreams = dataStreamsResponse.length > 0;
+ let userDsNames: string[] = [];
+
+ if (dsNames?.length) {
+ userDsNames = typeof dsNames === 'string' ? [dsNames] : dsNames;
+ } else if (!userDsNames.length && hasDataStreams) {
+ userDsNames = dataStreamsResponse.map((ds) => ds.name);
+ }
+
+ // If no data streams are found, return an empty response
+ if (!userDsNames.length) {
+ return response.ok({
+ body: {
+ metrics: {},
+ },
+ });
+ }
+
+ const metrics = await fetchMetricsFromAutoOps({
+ from,
+ to,
+ metricTypes: formatStringParams(metricTypes) as MetricTypes[],
+ dataStreams: formatStringParams(userDsNames),
+ });
+
+ return response.ok({
+ body: {
+ metrics,
+ },
+ });
+ } catch (error) {
+ logger.error(`Error retrieving usage metrics: ${error.message}`);
+ return errorHandler(logger, response, error);
+ }
+ };
+};
+
+const fetchMetricsFromAutoOps = async ({
+ from,
+ to,
+ metricTypes,
+ dataStreams,
+}: {
+ from: string;
+ to: string;
+ metricTypes: MetricTypes[];
+ dataStreams: string[];
+}) => {
+ // TODO: fetch data from autoOps using userDsNames
+ /*
+ const response = await axios.post('https://api.auto-ops.{region}.{csp}.cloud.elastic.co/monitoring/serverless/v1/projects/{project_id}/metrics', {
+ from: Date.parse(from),
+ to: Date.parse(to),
+ metric_types: metricTypes,
+ allowed_indices: dataStreams,
+ });
+ const { data } = response;*/
+ // mock data from autoOps https://github.com/elastic/autoops-services/blob/master/monitoring/service/specs/serverless_project_metrics_api.yaml
+ const mockData = {
+ metrics: {
+ ingest_rate: [
+ {
+ name: 'metrics-apache_spark.driver-default',
+ data: [
+ [1726858530000, 13756849],
+ [1726862130000, 14657904],
+ [1726865730000, 12798561],
+ [1726869330000, 13578213],
+ [1726872930000, 14123495],
+ [1726876530000, 13876548],
+ [1726880130000, 12894561],
+ [1726883730000, 14478953],
+ [1726887330000, 14678905],
+ [1726890930000, 13976547],
+ [1726894530000, 14568945],
+ [1726898130000, 13789561],
+ [1726901730000, 14478905],
+ [1726905330000, 13956423],
+ [1726908930000, 14598234],
+ ],
+ },
+ {
+ name: 'logs-apm.app.adservice-default',
+ data: [
+ [1726858530000, 12894623],
+ [1726862130000, 14436905],
+ [1726865730000, 13794805],
+ [1726869330000, 14048532],
+ [1726872930000, 14237495],
+ [1726876530000, 13745689],
+ [1726880130000, 13974562],
+ [1726883730000, 14234653],
+ [1726887330000, 14323479],
+ [1726890930000, 14023945],
+ [1726894530000, 14189673],
+ [1726898130000, 14247895],
+ [1726901730000, 14098324],
+ [1726905330000, 14478905],
+ [1726908930000, 14323894],
+ ],
+ },
+ {
+ name: 'metrics-apm.app.aws-lambdas-default',
+ data: [
+ [1726858530000, 12576413],
+ [1726862130000, 13956423],
+ [1726865730000, 14568945],
+ [1726869330000, 14234856],
+ [1726872930000, 14368942],
+ [1726876530000, 13897654],
+ [1726880130000, 14456989],
+ [1726883730000, 14568956],
+ [1726887330000, 13987562],
+ [1726890930000, 14567894],
+ [1726894530000, 14246789],
+ [1726898130000, 14567895],
+ [1726901730000, 14457896],
+ [1726905330000, 14567895],
+ [1726908930000, 13989456],
+ ],
+ },
+ ],
+ storage_retained: [
+ {
+ name: 'metrics-apache_spark.driver-default',
+ data: [
+ [1726858530000, 12576413],
+ [1726862130000, 13956423],
+ [1726865730000, 14568945],
+ [1726869330000, 14234856],
+ [1726872930000, 14368942],
+ [1726876530000, 13897654],
+ [1726880130000, 14456989],
+ [1726883730000, 14568956],
+ [1726887330000, 13987562],
+ [1726890930000, 14567894],
+ [1726894530000, 14246789],
+ [1726898130000, 14567895],
+ [1726901730000, 14457896],
+ [1726905330000, 14567895],
+ [1726908930000, 13989456],
+ ],
+ },
+ {
+ name: 'logs-apm.app.adservice-default',
+ data: [
+ [1726858530000, 12894623],
+ [1726862130000, 14436905],
+ [1726865730000, 13794805],
+ [1726869330000, 14048532],
+ [1726872930000, 14237495],
+ [1726876530000, 13745689],
+ [1726880130000, 13974562],
+ [1726883730000, 14234653],
+ [1726887330000, 14323479],
+ [1726890930000, 14023945],
+ [1726894530000, 14189673],
+ [1726898130000, 14247895],
+ [1726901730000, 14098324],
+ [1726905330000, 14478905],
+ [1726908930000, 14323894],
+ ],
+ },
+ {
+ name: 'metrics-apm.app.aws-lambdas-default',
+ data: [
+ [1726858530000, 12576413],
+ [1726862130000, 13956423],
+ [1726865730000, 14568945],
+ [1726869330000, 14234856],
+ [1726872930000, 14368942],
+ [1726876530000, 13897654],
+ [1726880130000, 14456989],
+ [1726883730000, 14568956],
+ [1726887330000, 13987562],
+ [1726890930000, 14567894],
+ [1726894530000, 14246789],
+ [1726898130000, 14567895],
+ [1726901730000, 14457896],
+ [1726905330000, 14567895],
+ [1726908930000, 13989456],
+ ],
+ },
+ ],
+ },
+ };
+ // Make sure data is what we expect
+ const validatedData = UsageMetricsResponseSchema.body().validate(mockData);
+
+ return validatedData.metrics;
+};
diff --git a/x-pack/plugins/data_usage/server/types/index.ts b/x-pack/plugins/data_usage/server/types/index.ts
new file mode 100644
index 00000000000000..6cc0ccaa93a6d1
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/types/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './types';
diff --git a/x-pack/plugins/data_usage/server/types/types.ts b/x-pack/plugins/data_usage/server/types/types.ts
new file mode 100644
index 00000000000000..c90beb184f020f
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/types/types.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type {
+ CoreRequestHandlerContext,
+ CustomRequestHandlerContext,
+ IRouter,
+ LoggerFactory,
+} from '@kbn/core/server';
+import { DeepReadonly } from 'utility-types';
+import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
+import { DataUsageConfigType } from '../config';
+
+export interface DataUsageSetupDependencies {
+ features: FeaturesPluginSetup;
+}
+
+/* eslint-disable @typescript-eslint/no-empty-interface*/
+export interface DataUsageStartDependencies {}
+
+export interface DataUsageServerSetup {}
+
+export interface DataUsageServerStart {}
+
+interface DataUsageApiRequestHandlerContext {
+ core: CoreRequestHandlerContext;
+}
+
+export type DataUsageRequestHandlerContext = CustomRequestHandlerContext<{
+ dataUsage: DataUsageApiRequestHandlerContext;
+}>;
+
+export type DataUsageRouter = IRouter;
+
+export interface DataUsageContext {
+ logFactory: LoggerFactory;
+ serverConfig: DeepReadonly;
+}
diff --git a/x-pack/plugins/data_usage/server/utils/custom_http_request_error.ts b/x-pack/plugins/data_usage/server/utils/custom_http_request_error.ts
new file mode 100644
index 00000000000000..a7f00a0e82a3d0
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/utils/custom_http_request_error.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export class CustomHttpRequestError extends Error {
+ constructor(
+ message: string,
+ public readonly statusCode: number = 500,
+ public readonly meta?: unknown
+ ) {
+ super(message);
+ // For debugging - capture name of subclasses
+ this.name = this.constructor.name;
+
+ if (meta instanceof Error) {
+ this.stack += `\n----- original error -----\n${meta.stack}`;
+ }
+ }
+}
diff --git a/x-pack/plugins/data_usage/server/utils/index.ts b/x-pack/plugins/data_usage/server/utils/index.ts
new file mode 100644
index 00000000000000..af46a18f61a799
--- /dev/null
+++ b/x-pack/plugins/data_usage/server/utils/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { CustomHttpRequestError } from './custom_http_request_error';
diff --git a/x-pack/plugins/data_usage/tsconfig.json b/x-pack/plugins/data_usage/tsconfig.json
index ebc023568cf887..cecbeb654db306 100644
--- a/x-pack/plugins/data_usage/tsconfig.json
+++ b/x-pack/plugins/data_usage/tsconfig.json
@@ -13,6 +13,7 @@
"kbn_references": [
"@kbn/core",
"@kbn/i18n",
+ "@kbn/data-plugin",
"@kbn/kibana-react-plugin",
"@kbn/management-plugin",
"@kbn/react-kibana-context-render",
@@ -21,6 +22,12 @@
"@kbn/share-plugin",
"@kbn/config-schema",
"@kbn/logging",
+ "@kbn/deeplinks-observability",
+ "@kbn/unified-search-plugin",
+ "@kbn/i18n-react",
+ "@kbn/core-http-browser",
+ "@kbn/core-chrome-browser",
+ "@kbn/features-plugin",
],
"exclude": ["target/**/*"]
}
From ca1d375348ef17721372612b408d9c31ac5d2494 Mon Sep 17 00:00:00 2001
From: Tiago Vila Verde
Date: Tue, 8 Oct 2024 15:27:09 +0200
Subject: [PATCH 04/50] [Entity Analytics] Integration tests: Kibana spaces
support in Entity Store (#194130)
### Summary
This PR adds integration tests for Kibana spaces support in the Entity
Store
---------
Co-authored-by: Elastic Machine
---
.../entity_store/entity_store_data_client.ts | 20 +-
.../package.json | 9 +-
.../trial_license_complete_tier/engine.ts | 89 ++-----
.../engine_nondefault_spaces.ts | 234 ++++++++++++++++++
.../trial_license_complete_tier/index.ts | 1 +
.../entity_analytics/utils/entity_store.ts | 102 ++++++--
6 files changed, 362 insertions(+), 93 deletions(-)
create mode 100644 x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
index e99358bca1ac81..e40a18fbe480fe 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts
@@ -68,7 +68,9 @@ export class EntityStoreDataClient {
const definition = getEntityDefinition(entityType, this.options.namespace);
- logger.info(`Initializing entity store for ${entityType}`);
+ logger.info(
+ `In namespace ${this.options.namespace}: Initializing entity store for ${entityType}`
+ );
const descriptor = await this.engineClient.init(entityType, definition, filter);
await entityClient.createEntityDefinition({
@@ -92,11 +94,13 @@ export class EntityStoreDataClient {
if (descriptor.status !== ENGINE_STATUS.STOPPED) {
throw new Error(
- `Cannot start Entity engine for ${entityType} when current status is: ${descriptor.status}`
+ `In namespace ${this.options.namespace}: Cannot start Entity engine for ${entityType} when current status is: ${descriptor.status}`
);
}
- this.options.logger.info(`Starting entity store for ${entityType}`);
+ this.options.logger.info(
+ `In namespace ${this.options.namespace}: Starting entity store for ${entityType}`
+ );
await this.options.entityClient.startEntityDefinition(definition);
return this.engineClient.update(definition.id, ENGINE_STATUS.STARTED);
@@ -109,11 +113,13 @@ export class EntityStoreDataClient {
if (descriptor.status !== ENGINE_STATUS.STARTED) {
throw new Error(
- `Cannot stop Entity engine for ${entityType} when current status is: ${descriptor.status}`
+ `In namespace ${this.options.namespace}: Cannot stop Entity engine for ${entityType} when current status is: ${descriptor.status}`
);
}
- this.options.logger.info(`Stopping entity store for ${entityType}`);
+ this.options.logger.info(
+ `In namespace ${this.options.namespace}: Stopping entity store for ${entityType}`
+ );
await this.options.entityClient.stopEntityDefinition(definition);
return this.engineClient.update(definition.id, ENGINE_STATUS.STOPPED);
@@ -130,7 +136,9 @@ export class EntityStoreDataClient {
public async delete(entityType: EntityType, deleteData: boolean) {
const { id } = getEntityDefinition(entityType, this.options.namespace);
- this.options.logger.info(`Deleting entity store for ${entityType}`);
+ this.options.logger.info(
+ `In namespace ${this.options.namespace}: Deleting entity store for ${entityType}`
+ );
await this.options.entityClient.deleteEntityDefinition({ id, deleteData });
await this.engineClient.delete(id);
diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json
index 726d6b61feed53..1649c79f52a7d8 100644
--- a/x-pack/test/security_solution_api_integration/package.json
+++ b/x-pack/test/security_solution_api_integration/package.json
@@ -63,7 +63,7 @@
"nlp_cleanup_task:essentials:qa:serverless:release": "npm run run-tests:genai:basic_essentials nlp_cleanup_task serverless qaEnv",
"entity_analytics:server:serverless": "npm run initialize-server:ea:trial_complete risk_engine serverless",
- "entity_analytics:runner:serverless": "npm run run-tests:ea:trial_complete risk_engine serverless serverlessEnv",
+ "entity_analytics:runner:serverless": "npm run run-tests:ea:trial_complete risk_engine serverless serverlessEnv --",
"entity_analytics:qa:serverless": "npm run run-tests:ea:trial_complete risk_engine serverless qaPeriodicEnv",
"entity_analytics:qa:serverless:release": "npm run run-tests:ea:trial_complete risk_engine serverless qaEnv",
"entity_analytics:server:ess": "npm run initialize-server:ea:trial_complete risk_engine ess",
@@ -76,6 +76,13 @@
"entity_analytics:essentials:server:ess": "npm run initialize-server:ea:basic_essentials risk_engine ess",
"entity_analytics:essentials:runner:ess": "npm run run-tests:ea:basic_essentials risk_engine ess essEnv",
+ "entity_analytics:entity_store:server:serverless": "npm run initialize-server:ea:trial_complete entity_store serverless",
+ "entity_analytics:entity_store:runner:serverless": "npm run run-tests:ea:trial_complete entity_store serverless serverlessEnv",
+ "entity_analytics:entity_store:qa:serverless": "npm run run-tests:ea:trial_complete entity_store serverless qaPeriodicEnv",
+ "entity_analytics:entity_store:qa:serverless:release": "npm run run-tests:ea:trial_complete entity_store serverless qaEnv",
+ "entity_analytics:entity_store:server:ess": "npm run initialize-server:ea:trial_complete entity_store ess",
+ "entity_analytics:entity_store:runner:ess": "npm run run-tests:ea:trial_complete entity_store ess essEnv --",
+
"edr_workflows:artifacts:server:serverless": "npm run initialize-server:edr-workflows artifacts serverless",
"edr_workflows:artifacts:runner:serverless": "npm run run-tests:edr-workflows artifacts serverless serverlessEnv",
"edr_workflows:artifacts:qa:serverless": "npm run run-tests:edr-workflows artifacts serverless qaPeriodicEnv",
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts
index 8d57ff428e5077..99d84fbc5427bf 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts
@@ -6,104 +6,57 @@
*/
import expect from '@kbn/expect';
-import { EntityType } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/common.gen';
+
import { FtrProviderContext } from '../../../../ftr_provider_context';
-import { cleanEngines } from '../../utils';
+import { EntityStoreUtils } from '../../utils';
export default ({ getService }: FtrProviderContext) => {
const api = getService('securitySolutionApi');
- const es = getService('es');
-
- const initEntityEngineForEntityType = async (entityType: EntityType) => {
- return api
- .initEntityEngine({
- params: { entityType },
- body: {},
- })
- .expect(200);
- };
-
- const expectTransformExists = async (transformId: string) => {
- return expectTransformStatus(transformId, true);
- };
-
- const expectTransformNotFound = async (transformId: string, attempts: number = 5) => {
- return expectTransformStatus(transformId, false);
- };
-
- const expectTransformStatus = async (
- transformId: string,
- exists: boolean,
- attempts: number = 5,
- delayMs: number = 2000
- ) => {
- let currentAttempt = 1;
- while (currentAttempt <= attempts) {
- try {
- await es.transform.getTransform({ transform_id: transformId });
- if (!exists) {
- throw new Error(`Expected transform ${transformId} to not exist, but it does`);
- }
- return; // Transform exists, exit the loop
- } catch (e) {
- if (currentAttempt === attempts) {
- if (exists) {
- throw new Error(`Expected transform ${transformId} to exist, but it does not: ${e}`);
- } else {
- return; // Transform does not exist, exit the loop
- }
- }
- await new Promise((resolve) => setTimeout(resolve, delayMs));
- currentAttempt++;
- }
- }
- };
-
- const expectTransformsExist = async (transformIds: string[]) =>
- Promise.all(transformIds.map((id) => expectTransformExists(id)));
+
+ const utils = EntityStoreUtils(getService);
describe('@ess @serverless @skipInServerlessMKI Entity Store Engine APIs', () => {
before(async () => {
- await cleanEngines({ getService });
+ await utils.cleanEngines();
});
describe('init', () => {
afterEach(async () => {
- await cleanEngines({ getService });
+ await utils.cleanEngines();
});
it('should have installed the expected user resources', async () => {
- await initEntityEngineForEntityType('user');
+ await utils.initEntityEngineForEntityType('user');
const expectedTransforms = [
'entities-v1-history-ea_default_user_entity_store',
'entities-v1-latest-ea_default_user_entity_store',
];
- await expectTransformsExist(expectedTransforms);
+ await utils.expectTransformsExist(expectedTransforms);
});
it('should have installed the expected host resources', async () => {
- await initEntityEngineForEntityType('host');
+ await utils.initEntityEngineForEntityType('host');
const expectedTransforms = [
'entities-v1-history-ea_default_host_entity_store',
'entities-v1-latest-ea_default_host_entity_store',
];
- await expectTransformsExist(expectedTransforms);
+ await utils.expectTransformsExist(expectedTransforms);
});
});
describe('get and list', () => {
before(async () => {
await Promise.all([
- initEntityEngineForEntityType('host'),
- initEntityEngineForEntityType('user'),
+ utils.initEntityEngineForEntityType('host'),
+ utils.initEntityEngineForEntityType('user'),
]);
});
after(async () => {
- await cleanEngines({ getService });
+ await utils.cleanEngines();
});
describe('get', () => {
@@ -169,11 +122,11 @@ export default ({ getService }: FtrProviderContext) => {
describe('start and stop', () => {
before(async () => {
- await initEntityEngineForEntityType('host');
+ await utils.initEntityEngineForEntityType('host');
});
after(async () => {
- await cleanEngines({ getService });
+ await utils.cleanEngines();
});
it('should stop the entity engine', async () => {
@@ -211,7 +164,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('delete', () => {
it('should delete the host entity engine', async () => {
- await initEntityEngineForEntityType('host');
+ await utils.initEntityEngineForEntityType('host');
await api
.deleteEntityEngine({
@@ -220,12 +173,12 @@ export default ({ getService }: FtrProviderContext) => {
})
.expect(200);
- await expectTransformNotFound('entities-v1-history-ea_host_entity_store');
- await expectTransformNotFound('entities-v1-latest-ea_host_entity_store');
+ await utils.expectTransformNotFound('entities-v1-history-ea_host_entity_store');
+ await utils.expectTransformNotFound('entities-v1-latest-ea_host_entity_store');
});
it('should delete the user entity engine', async () => {
- await initEntityEngineForEntityType('user');
+ await utils.initEntityEngineForEntityType('user');
await api
.deleteEntityEngine({
@@ -234,8 +187,8 @@ export default ({ getService }: FtrProviderContext) => {
})
.expect(200);
- await expectTransformNotFound('entities-v1-history-ea_user_entity_store');
- await expectTransformNotFound('entities-v1-latest-ea_user_entity_store');
+ await utils.expectTransformNotFound('entities-v1-history-ea_user_entity_store');
+ await utils.expectTransformNotFound('entities-v1-latest-ea_user_entity_store');
});
});
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts
new file mode 100644
index 00000000000000..112c8b8b215114
--- /dev/null
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts
@@ -0,0 +1,234 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import { v4 as uuidv4 } from 'uuid';
+import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces';
+import { EntityStoreUtils } from '../../utils';
+export default ({ getService }: FtrProviderContextWithSpaces) => {
+ const api = getService('securitySolutionApi');
+ const spaces = getService('spaces');
+ const namespace = uuidv4().substring(0, 8);
+
+ const utils = EntityStoreUtils(getService, namespace);
+
+ describe('@ess Entity Store Engine APIs in non-default space', () => {
+ before(async () => {
+ await utils.cleanEngines();
+ await spaces.create({
+ id: namespace,
+ name: namespace,
+ disabledFeatures: [],
+ });
+ });
+
+ after(async () => {
+ await spaces.delete(namespace);
+ });
+
+ describe('init', () => {
+ afterEach(async () => {
+ await utils.cleanEngines();
+ });
+
+ it('should have installed the expected user resources', async () => {
+ await utils.initEntityEngineForEntityType('user');
+
+ const expectedTransforms = [
+ `entities-v1-history-ea_${namespace}_user_entity_store`,
+ `entities-v1-latest-ea_${namespace}_user_entity_store`,
+ ];
+
+ await utils.expectTransformsExist(expectedTransforms);
+ });
+
+ it('should have installed the expected host resources', async () => {
+ await utils.initEntityEngineForEntityType('host');
+
+ const expectedTransforms = [
+ `entities-v1-history-ea_${namespace}_host_entity_store`,
+ `entities-v1-latest-ea_${namespace}_host_entity_store`,
+ ];
+
+ await utils.expectTransformsExist(expectedTransforms);
+ });
+ });
+
+ describe('get and list', () => {
+ before(async () => {
+ await Promise.all([
+ utils.initEntityEngineForEntityType('host'),
+ utils.initEntityEngineForEntityType('user'),
+ ]);
+ });
+
+ after(async () => {
+ await utils.cleanEngines();
+ });
+
+ describe('get', () => {
+ it('should return the host entity engine', async () => {
+ const getResponse = await api
+ .getEntityEngine(
+ {
+ params: { entityType: 'host' },
+ },
+ namespace
+ )
+ .expect(200);
+
+ expect(getResponse.body).to.eql({
+ status: 'started',
+ type: 'host',
+ indexPattern:
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*',
+ filter: '',
+ });
+ });
+
+ it('should return the user entity engine', async () => {
+ const getResponse = await api
+ .getEntityEngine(
+ {
+ params: { entityType: 'user' },
+ },
+ namespace
+ )
+ .expect(200);
+
+ expect(getResponse.body).to.eql({
+ status: 'started',
+ type: 'user',
+ indexPattern:
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*',
+ filter: '',
+ });
+ });
+ });
+
+ describe('list', () => {
+ it('should return the list of entity engines', async () => {
+ const { body } = await api.listEntityEngines(namespace).expect(200);
+
+ // @ts-expect-error body is any
+ const sortedEngines = body.engines.sort((a, b) => a.type.localeCompare(b.type));
+
+ expect(sortedEngines).to.eql([
+ {
+ status: 'started',
+ type: 'host',
+ indexPattern:
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*',
+ filter: '',
+ },
+ {
+ status: 'started',
+ type: 'user',
+ indexPattern:
+ 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*',
+ filter: '',
+ },
+ ]);
+ });
+ });
+ });
+
+ describe('start and stop', () => {
+ before(async () => {
+ await utils.initEntityEngineForEntityType('host');
+ });
+
+ after(async () => {
+ await utils.cleanEngines();
+ });
+
+ it('should stop the entity engine', async () => {
+ await api
+ .stopEntityEngine(
+ {
+ params: { entityType: 'host' },
+ },
+ namespace
+ )
+ .expect(200);
+
+ const { body } = await api
+ .getEntityEngine(
+ {
+ params: { entityType: 'host' },
+ },
+ namespace
+ )
+ .expect(200);
+
+ expect(body.status).to.eql('stopped');
+ });
+
+ it('should start the entity engine', async () => {
+ await api
+ .startEntityEngine(
+ {
+ params: { entityType: 'host' },
+ },
+ namespace
+ )
+ .expect(200);
+
+ const { body } = await api
+ .getEntityEngine(
+ {
+ params: { entityType: 'host' },
+ },
+ namespace
+ )
+ .expect(200);
+
+ expect(body.status).to.eql('started');
+ });
+ });
+
+ describe('delete', () => {
+ it('should delete the host entity engine', async () => {
+ await utils.initEntityEngineForEntityType('host');
+
+ await api
+ .deleteEntityEngine(
+ {
+ params: { entityType: 'host' },
+ query: { data: true },
+ },
+ namespace
+ )
+ .expect(200);
+
+ await utils.expectTransformNotFound(
+ `entities-v1-history-ea_${namespace}_host_entity_store`
+ );
+ await utils.expectTransformNotFound(`entities-v1-latest-ea_${namespace}_host_entity_store`);
+ });
+
+ it('should delete the user entity engine', async () => {
+ await utils.initEntityEngineForEntityType('user');
+
+ await api
+ .deleteEntityEngine(
+ {
+ params: { entityType: 'user' },
+ query: { data: true },
+ },
+ namespace
+ )
+ .expect(200);
+
+ await utils.expectTransformNotFound(
+ `entities-v1-history-ea_${namespace}_user_entity_store`
+ );
+ await utils.expectTransformNotFound(`entities-v1-latest-ea_${namespace}_user_entity_store`);
+ });
+ });
+ });
+};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts
index 6e730b465350aa..ec9c7a8f1dcbab 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts
@@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('Entity Analytics - Entity Store', function () {
loadTestFile(require.resolve('./entities_list'));
loadTestFile(require.resolve('./engine'));
+ loadTestFile(require.resolve('./engine_nondefault_spaces'));
});
}
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts
index 3f0ba0698a4942..9dc1807d7263c6 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts
@@ -5,29 +5,95 @@
* 2.0.
*/
+import { EntityType } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/common.gen';
+
import { FtrProviderContext } from '../../../../api_integration/ftr_provider_context';
-export const cleanEngines = async ({
- getService,
-}: {
- getService: FtrProviderContext['getService'];
-}) => {
- const log = getService('log');
+export const EntityStoreUtils = (
+ getService: FtrProviderContext['getService'],
+ namespace?: string
+) => {
const api = getService('securitySolutionApi');
+ const es = getService('es');
+ const log = getService('log');
+
+ log.debug(`EntityStoreUtils namespace: ${namespace}`);
+
+ const cleanEngines = async () => {
+ const { body } = await api.listEntityEngines(namespace).expect(200);
- const { body } = await api.listEntityEngines().expect(200);
+ // @ts-expect-error body is any
+ const engineTypes = body.engines.map((engine) => engine.type);
- // @ts-expect-error body is any
- const engineTypes = body.engines.map((engine) => engine.type);
+ log.info(`Cleaning engines: ${engineTypes.join(', ')}`);
+ try {
+ await Promise.all(
+ engineTypes.map((entityType: 'user' | 'host') =>
+ api.deleteEntityEngine({ params: { entityType }, query: { data: true } }, namespace)
+ )
+ );
+ } catch (e) {
+ log.warning(`Error deleting engines: ${e.message}`);
+ }
+ };
- log.info(`Cleaning engines: ${engineTypes.join(', ')}`);
- try {
- await Promise.all(
- engineTypes.map((entityType: 'user' | 'host') =>
- api.deleteEntityEngine({ params: { entityType }, query: { data: true } })
+ const initEntityEngineForEntityType = async (entityType: EntityType) => {
+ log.info(`Initializing engine for entity type ${entityType} in namespace ${namespace}`);
+ return api
+ .initEntityEngine(
+ {
+ params: { entityType },
+ body: {},
+ },
+ namespace
)
- );
- } catch (e) {
- log.warning(`Error deleting engines: ${e.message}`);
- }
+ .expect(200);
+ };
+
+ const expectTransformStatus = async (
+ transformId: string,
+ exists: boolean,
+ attempts: number = 5,
+ delayMs: number = 2000
+ ) => {
+ let currentAttempt = 1;
+ while (currentAttempt <= attempts) {
+ try {
+ await es.transform.getTransform({ transform_id: transformId });
+ if (!exists) {
+ throw new Error(`Expected transform ${transformId} to not exist, but it does`);
+ }
+ return; // Transform exists, exit the loop
+ } catch (e) {
+ if (currentAttempt === attempts) {
+ if (exists) {
+ throw new Error(`Expected transform ${transformId} to exist, but it does not: ${e}`);
+ } else {
+ return; // Transform does not exist, exit the loop
+ }
+ }
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
+ currentAttempt++;
+ }
+ }
+ };
+
+ const expectTransformNotFound = async (transformId: string, attempts: number = 5) => {
+ return expectTransformStatus(transformId, false);
+ };
+ const expectTransformExists = async (transformId: string) => {
+ return expectTransformStatus(transformId, true);
+ };
+
+ const expectTransformsExist = async (transformIds: string[]) =>
+ Promise.all(transformIds.map((id) => expectTransformExists(id)));
+
+ return {
+ cleanEngines,
+ initEntityEngineForEntityType,
+ expectTransformStatus,
+ expectTransformNotFound,
+ expectTransformExists,
+ expectTransformsExist,
+ };
};
From ef4755a063e5e08af3ebd44c2a866ac804eabcd4 Mon Sep 17 00:00:00 2001
From: Konrad Szwarc
Date: Tue, 8 Oct 2024 15:34:27 +0200
Subject: [PATCH 05/50] [EDR Workflows] Deprecate public endpoint/suggestions
api endpoint in favour of an internal one (#194832)
New internal GET `/internal/api/endpoint/suggestions/{suggestion_type}`
route.
Current public GET `/api/endpoint/suggestions/{suggestion_type}` route
is set to deprecated.
UI uses now the internal GET
`/internal/api/endpoint/suggestions/{suggestion_type}` api route
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../output/kibana.serverless.staging.yaml | 1 +
oas_docs/output/kibana.serverless.yaml | 1 +
oas_docs/output/kibana.staging.yaml | 1 +
oas_docs/output/kibana.yaml | 1 +
.../suggestions/get_suggestions.schema.yaml | 1 +
.../common/endpoint/constants.ts | 3 ++
...agement_api_2023_10_31.bundled.schema.yaml | 1 +
...agement_api_2023_10_31.bundled.schema.yaml | 1 +
.../event_filters/service/api_client.test.ts | 44 +++++++++++++++++++
.../pages/event_filters/service/api_client.ts | 6 +--
.../exceptions_list_api_client.test.ts | 4 +-
.../endpoint/routes/suggestions/index.test.ts | 9 ++--
.../endpoint/routes/suggestions/index.ts | 27 +++++++++++-
13 files changed, 91 insertions(+), 9 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.test.ts
diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml
index 8bd9bd198e5e1d..66790ef3f9c178 100644
--- a/oas_docs/output/kibana.serverless.staging.yaml
+++ b/oas_docs/output/kibana.serverless.staging.yaml
@@ -8096,6 +8096,7 @@ paths:
- Security Endpoint Management API
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
operationId: GetEndpointSuggestions
parameters:
- in: path
diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml
index 8bd9bd198e5e1d..66790ef3f9c178 100644
--- a/oas_docs/output/kibana.serverless.yaml
+++ b/oas_docs/output/kibana.serverless.yaml
@@ -8096,6 +8096,7 @@ paths:
- Security Endpoint Management API
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
operationId: GetEndpointSuggestions
parameters:
- in: path
diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml
index 1ceeae519da724..7dbb9571f02720 100644
--- a/oas_docs/output/kibana.staging.yaml
+++ b/oas_docs/output/kibana.staging.yaml
@@ -11468,6 +11468,7 @@ paths:
- Security Endpoint Management API
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
operationId: GetEndpointSuggestions
parameters:
- in: path
diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml
index 1ceeae519da724..7dbb9571f02720 100644
--- a/oas_docs/output/kibana.yaml
+++ b/oas_docs/output/kibana.yaml
@@ -11468,6 +11468,7 @@ paths:
- Security Endpoint Management API
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
operationId: GetEndpointSuggestions
parameters:
- in: path
diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml
index 573f9c0e3992f3..08006d91045ff4 100644
--- a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml
+++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.schema.yaml
@@ -5,6 +5,7 @@ info:
paths:
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
summary: Get suggestions
operationId: GetEndpointSuggestions
x-codegen-enabled: true
diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts
index 276d2fa32da76e..0e7218f0d7d413 100644
--- a/x-pack/plugins/security_solution/common/endpoint/constants.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts
@@ -60,6 +60,7 @@ export const FILE_STORAGE_DATA_INDEX = getFileDataIndexName('endpoint');
// Location from where all Endpoint related APIs are mounted
export const BASE_ENDPOINT_ROUTE = '/api/endpoint';
+export const BASE_INTERNAL_ENDPOINT_ROUTE = `/internal${BASE_ENDPOINT_ROUTE}`;
// Endpoint API routes
export const HOST_METADATA_LIST_ROUTE = `${BASE_ENDPOINT_ROUTE}/metadata`;
@@ -72,7 +73,9 @@ export const AGENT_POLICY_SUMMARY_ROUTE = `${BASE_POLICY_ROUTE}/summaries`;
export const PROTECTION_UPDATES_NOTE_ROUTE = `${BASE_ENDPOINT_ROUTE}/protection_updates_note/{package_policy_id}`;
/** Suggestions routes */
+/** @deprecated public route, use {@link SUGGESTIONS_INTERNAL_ROUTE} internal route */
export const SUGGESTIONS_ROUTE = `${BASE_ENDPOINT_ROUTE}/suggestions/{suggestion_type}`;
+export const SUGGESTIONS_INTERNAL_ROUTE = `${BASE_INTERNAL_ENDPOINT_ROUTE}/suggestions/{suggestion_type}`;
/**
* Action Response Routes
diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml
index ae35a302cbb42b..5d03e10969e14d 100644
--- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml
+++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml
@@ -545,6 +545,7 @@ paths:
- Security Endpoint Management API
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
operationId: GetEndpointSuggestions
parameters:
- in: path
diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml
index df98520fe783f3..f5c2d290af33c3 100644
--- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml
+++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml
@@ -495,6 +495,7 @@ paths:
- Security Endpoint Management API
/api/endpoint/suggestions/{suggestion_type}:
post:
+ deprecated: true
operationId: GetEndpointSuggestions
parameters:
- in: path
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.test.ts
new file mode 100644
index 00000000000000..9810bf6d290605
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.test.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license 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 { HttpSetup } from '@kbn/core-http-browser';
+import { EventFiltersApiClient } from './api_client';
+import { coreMock } from '@kbn/core/public/mocks';
+import { SUGGESTIONS_INTERNAL_ROUTE } from '../../../../../common/endpoint/constants';
+import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
+
+describe('EventFiltersApiClient', () => {
+ let fakeHttpServices: jest.Mocked;
+ let eventFiltersApiClient: EventFiltersApiClient;
+
+ beforeAll(() => {
+ fakeHttpServices = coreMock.createStart().http as jest.Mocked;
+ eventFiltersApiClient = new EventFiltersApiClient(fakeHttpServices);
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call the SUGGESTIONS_INTERNAL_ROUTE with correct URL and body', async () => {
+ await eventFiltersApiClient.getSuggestions({
+ field: 'host.name',
+ query: 'test',
+ });
+
+ expect(fakeHttpServices.post).toHaveBeenCalledWith(
+ resolvePathVariables(SUGGESTIONS_INTERNAL_ROUTE, { suggestion_type: 'eventFilters' }),
+ {
+ version: '1',
+ body: JSON.stringify({
+ field: 'host.name',
+ query: 'test',
+ }),
+ }
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.ts
index e7c5e53e34274c..48bfcb9d848d1f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.ts
@@ -12,8 +12,8 @@ import type {
UpdateExceptionListItemSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import { removeIdFromExceptionItemsEntries } from '@kbn/securitysolution-list-hooks';
+import { SUGGESTIONS_INTERNAL_ROUTE } from '../../../../../common/endpoint/constants';
import type { EndpointSuggestionsBody } from '../../../../../common/api/endpoint';
-import { SUGGESTIONS_ROUTE } from '../../../../../common/endpoint/constants';
import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
import { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client';
import { EVENT_FILTER_LIST_DEFINITION } from '../constants';
@@ -55,9 +55,9 @@ export class EventFiltersApiClient extends ExceptionsListApiClient {
*/
async getSuggestions(body: EndpointSuggestionsBody): Promise {
const result: string[] = await this.getHttp().post(
- resolvePathVariables(SUGGESTIONS_ROUTE, { suggestion_type: 'eventFilters' }),
+ resolvePathVariables(SUGGESTIONS_INTERNAL_ROUTE, { suggestion_type: 'eventFilters' }),
{
- version: this.version,
+ version: '1',
body: JSON.stringify(body),
}
);
diff --git a/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts b/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts
index 213b8da54dcd00..0424ff8ce6db34 100644
--- a/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts
+++ b/x-pack/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts
@@ -59,9 +59,9 @@ describe('Exceptions List Api Client', () => {
);
});
- describe('Wen getting an instance', () => {
+ describe('When getting an instance', () => {
/**
- * ATENTION: Skipping or modifying this test may cause the other test fails because it's creating the initial Singleton instance.
+ * ATTENTION: Skipping or modifying this test may cause the other test fails because it's creating the initial Singleton instance.
* If you want to run tests individually, add this one to the execution with the .only method
*/
it('New instance is created the first time and the create list method is called', () => {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.test.ts
index d8df4fc7131e06..e122367303340d 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.test.ts
@@ -39,7 +39,10 @@ import {
} from '.';
import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator';
import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks';
-import { eventsIndexPattern, SUGGESTIONS_ROUTE } from '../../../../common/endpoint/constants';
+import {
+ eventsIndexPattern,
+ SUGGESTIONS_INTERNAL_ROUTE,
+} from '../../../../common/endpoint/constants';
import { EndpointAppContextService } from '../../endpoint_app_context_services';
jest.mock('@kbn/unified-search-plugin/server/autocomplete/terms_enum', () => {
@@ -184,7 +187,7 @@ describe('when calling the Suggestions route handler', () => {
routerMock,
'post',
routePrefix,
- '2023-10-31'
+ '1'
);
await routeHandler(ctx as unknown as RequestHandlerContext, mockRequest, mockResponse);
@@ -192,7 +195,7 @@ describe('when calling the Suggestions route handler', () => {
});
it('should respond with forbidden', async () => {
- await callRoute(SUGGESTIONS_ROUTE, {
+ await callRoute(SUGGESTIONS_INTERNAL_ROUTE, {
params: { suggestion_type: 'eventFilters' },
authz: { canReadEventFilters: true, canWriteEventFilters: false },
});
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts
index 6e84e101d25062..a4853d9772ad7b 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/suggestions/index.ts
@@ -21,7 +21,11 @@ import type {
SecuritySolutionRequestHandlerContext,
} from '../../../types';
import type { EndpointAppContext } from '../../types';
-import { eventsIndexPattern, SUGGESTIONS_ROUTE } from '../../../../common/endpoint/constants';
+import {
+ eventsIndexPattern,
+ SUGGESTIONS_INTERNAL_ROUTE,
+ SUGGESTIONS_ROUTE,
+} from '../../../../common/endpoint/constants';
import { withEndpointAuthz } from '../with_endpoint_authz';
import { errorHandler } from '../error_handler';
@@ -39,6 +43,7 @@ export function registerEndpointSuggestionsRoutes(
access: 'public',
path: SUGGESTIONS_ROUTE,
options: { authRequired: true, tags: ['access:securitySolution'] },
+ deprecated: true,
})
.addVersion(
{
@@ -53,6 +58,26 @@ export function registerEndpointSuggestionsRoutes(
getEndpointSuggestionsRequestHandler(config$, getLogger(endpointContext))
)
);
+
+ router.versioned
+ .post({
+ access: 'internal',
+ path: SUGGESTIONS_INTERNAL_ROUTE,
+ options: { authRequired: true, tags: ['access:securitySolution'] },
+ })
+ .addVersion(
+ {
+ version: '1',
+ validate: {
+ request: EndpointSuggestionsSchema,
+ },
+ },
+ withEndpointAuthz(
+ { any: ['canWriteEventFilters'] },
+ endpointContext.logFactory.get('endpointSuggestions'),
+ getEndpointSuggestionsRequestHandler(config$, getLogger(endpointContext))
+ )
+ );
}
export const getEndpointSuggestionsRequestHandler = (
From 8281517ef5ede44519dbb52abaf59d4f86d9396e Mon Sep 17 00:00:00 2001
From: Kevin Delemme
Date: Tue, 8 Oct 2024 10:21:47 -0400
Subject: [PATCH 06/50] chore(slo): add access:public missing options for
public routes (#195114)
---
.../observability_solution/slo/server/routes/slo/route.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts
index 703c8aab5a684a..838adc72cfd08c 100644
--- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts
+++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts
@@ -516,6 +516,7 @@ const deleteSloInstancesRoute = createSloServerRoute({
endpoint: 'POST /api/observability/slos/_delete_instances 2023-10-31',
options: {
tags: ['access:slo_write'],
+ access: 'public',
},
params: deleteSLOInstancesParamsSchema,
handler: async ({ context, params }) => {
@@ -532,6 +533,7 @@ const findSloDefinitionsRoute = createSloServerRoute({
endpoint: 'GET /api/observability/slos/_definitions 2023-10-31',
options: {
tags: ['access:slo_read'],
+ access: 'public',
},
params: findSloDefinitionsParamsSchema,
handler: async ({ context, params, logger }) => {
From d20c579304b5fd212971bd9ef80cfbbda0932d94 Mon Sep 17 00:00:00 2001
From: Angela Chuang <6295984+angorayc@users.noreply.github.com>
Date: Tue, 8 Oct 2024 15:22:09 +0100
Subject: [PATCH 07/50] [SecuritySolution] Allow custom sorting on integration
cards (#195397)
Part of https://github.com/elastic/kibana/pull/193131
It shows the customised cards without applying the default sorting under
the `recommended` tab:
Added two optional props:
1. `calloutTopSpacerSize` - Props to decide the size of the spacer above
callout. Security Solution uses this prop to customize the size of the
spacer
2. `sortByFeaturedIntegrations` - Customizing whether to sort by the
default featured integrations' categories. Security Solution has `custom
sorting logic`
Featured cards:
1. AWS
3. GCP
4. Azure
5. Elastic Defend
6. CrowdStrike (to promote our extended protections/3rd party EDR
support)
7. Wiz (or another cloud integration to promote extended protections)
8. Network Packet Capture
9. Osquery Manager
10. ~**Cloud Asset Inventory (need to confirm that's confirmed for
8.16)** - Currently Not Found any integration matched~
---
.../epm/components/package_list_grid/grid.tsx | 1 -
.../epm/components/package_list_grid/index.tsx | 15 +++++++++++----
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx
index 959b5682792b99..3d7b5348607a0c 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/grid.tsx
@@ -68,7 +68,6 @@ export const GridColumn = ({
}: GridColumnProps) => {
const itemsSizeRefs = useRef(new Map());
const listRef = useRef(null);
-
const onHeightChange = useCallback((index: number, size: number) => {
itemsSizeRefs.current.set(index, size);
if (listRef.current) {
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx
index be2b873c317db4..ba90ed6509f95d 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx
@@ -61,6 +61,8 @@ export interface PackageListGridProps {
setUrlandReplaceHistory: (params: IntegrationsURLParameters) => void;
setUrlandPushHistory: (params: IntegrationsURLParameters) => void;
callout?: JSX.Element | null;
+ // Props to decide the size of the spacer above callout. Security Solution uses this prop to customize the size of the spacer
+ calloutTopSpacerSize?: 's' | 'm' | 'xs' | 'l' | 'xl' | 'xxl';
// Props used only in AvailablePackages component:
showCardLabels?: boolean;
title?: string;
@@ -70,6 +72,8 @@ export interface PackageListGridProps {
showMissingIntegrationMessage?: boolean;
showControls?: boolean;
showSearchTools?: boolean;
+ // Customizing whether to sort by the default featured integrations' categories. Security Solution has custom sorting logic
+ sortByFeaturedIntegrations?: boolean;
spacer?: boolean;
// Security Solution sends the id to determine which element to scroll when the user interacting with the package list
scrollElementId?: string;
@@ -92,7 +96,9 @@ export const PackageListGrid: FunctionComponent = ({
setUrlandReplaceHistory,
setUrlandPushHistory,
showMissingIntegrationMessage = false,
+ sortByFeaturedIntegrations = true,
callout,
+ calloutTopSpacerSize = 'l', // Default EUI spacer size
showCardLabels = true,
showControls = true,
showSearchTools = true,
@@ -141,9 +147,10 @@ export const PackageListGrid: FunctionComponent = ({
)
: list;
- return promoteFeaturedIntegrations(filteredList, selectedCategory);
- }, [isLoading, list, localSearchRef, searchTerm, selectedCategory]);
-
+ return sortByFeaturedIntegrations
+ ? promoteFeaturedIntegrations(filteredList, selectedCategory)
+ : filteredList;
+ }, [isLoading, list, localSearchRef, searchTerm, selectedCategory, sortByFeaturedIntegrations]);
const splitSubcategories = (
subcategories: CategoryFacet[] | undefined
): { visibleSubCategories?: CategoryFacet[]; hiddenSubCategories?: CategoryFacet[] } => {
@@ -270,7 +277,7 @@ export const PackageListGrid: FunctionComponent = ({
) : null}
{callout ? (
<>
-
+
{callout}
>
) : null}
From d573915dd30c62adb780728ab83668285f5bd64d Mon Sep 17 00:00:00 2001
From: Rodney Norris
Date: Tue, 8 Oct 2024 09:29:59 -0500
Subject: [PATCH 08/50] [Search][Onboarding] Default home to Global Empty State
(#195142)
## Summary
Updated the ES3 (Serverless Search) default home route to be the global
empty state, when `search_indices` is enabled.
Moved the getting started page, the current homepage, from
`/app/elasticsearch` to `/app/elasticsearch/getting_started`
This required adding a redirect for `/app/elasticsearch` to
`/app/elasticsearch/start`.
After we enabled `search_indices` by default for ES3, we can remove the
conditional logic added by this PR.
### Screenshots
ES3 Home With search indices config FF enabled
![image](https://github.com/user-attachments/assets/9a2227c0-8ec3-4e98-ba5c-08cebf8d3df4)
ES3 Home with search indices config FF disabled
![image](https://github.com/user-attachments/assets/68bb6f36-f754-4f6b-9637-cf419ef21945)
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
---
packages/deeplinks/search/constants.ts | 2 +
packages/deeplinks/search/deep_links.ts | 8 +-
x-pack/plugins/search_indices/common/index.ts | 4 +
.../public/components/start/create_index.tsx | 33 ++++----
.../components/start/elasticsearch_start.tsx | 2 -
.../start/hooks/use_indices_redirect.tsx | 25 +++++-
.../plugins/search_indices/public/plugin.ts | 18 ++--
.../plugins/search_indices/public/routes.ts | 3 +
x-pack/plugins/search_indices/public/types.ts | 13 ++-
x-pack/plugins/search_indices/tsconfig.json | 4 +-
.../public/navigation_tree.ts | 33 +++++++-
.../serverless_search/public/plugin.ts | 37 +++++++--
.../plugins/serverless_search/public/types.ts | 6 ++
.../plugins/serverless_search/tsconfig.json | 1 +
.../test_serverless/functional/config.base.ts | 1 +
.../services/svl_search_navigation.ts | 14 +++-
.../search/config.feature_flags.ts | 11 +++
.../functional/test_suites/search/config.ts | 11 +++
.../test_suites/search/elasticsearch_start.ts | 82 ++++++++++---------
.../{landing_page.ts => getting_started.ts} | 8 +-
.../test_suites/search/index.feature_flags.ts | 1 +
.../functional/test_suites/search/index.ts | 2 +-
.../test_suites/search/search_index_detail.ts | 3 +-
x-pack/test_serverless/shared/types/index.ts | 1 +
24 files changed, 237 insertions(+), 86 deletions(-)
rename x-pack/test_serverless/functional/test_suites/search/{landing_page.ts => getting_started.ts} (95%)
diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts
index 76a9834d061e12..c4a598145c87c6 100644
--- a/packages/deeplinks/search/constants.ts
+++ b/packages/deeplinks/search/constants.ts
@@ -19,3 +19,5 @@ export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors';
export const SERVERLESS_ES_SEARCH_PLAYGROUND_ID = 'searchPlayground';
export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpoints';
export const SEARCH_HOMEPAGE = 'searchHomepage';
+export const SEARCH_INDICES_START = 'elasticsearchStart';
+export const SEARCH_INDICES = 'elasticsearchIndices';
diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts
index 6d42b5603f212c..98703f18ac3fb2 100644
--- a/packages/deeplinks/search/deep_links.ts
+++ b/packages/deeplinks/search/deep_links.ts
@@ -20,6 +20,8 @@ import {
SERVERLESS_ES_SEARCH_PLAYGROUND_ID,
SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID,
SEARCH_HOMEPAGE,
+ SEARCH_INDICES_START,
+ SEARCH_INDICES,
} from './constants';
export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID;
@@ -34,6 +36,8 @@ export type ConnectorsId = typeof SERVERLESS_ES_CONNECTORS_ID;
export type SearchPlaygroundId = typeof SERVERLESS_ES_SEARCH_PLAYGROUND_ID;
export type SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID;
export type SearchHomepage = typeof SEARCH_HOMEPAGE;
+export type SearchStart = typeof SEARCH_INDICES_START;
+export type SearchIndices = typeof SEARCH_INDICES;
export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers';
@@ -59,4 +63,6 @@ export type DeepLinkId =
| `${EnterpriseSearchContentApp}:${ContentLinkId}`
| `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}`
| `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`
- | `${EnterpriseSearchRelevanceApp}:${RelevanceLinkId}`;
+ | `${EnterpriseSearchRelevanceApp}:${RelevanceLinkId}`
+ | SearchStart
+ | SearchIndices;
diff --git a/x-pack/plugins/search_indices/common/index.ts b/x-pack/plugins/search_indices/common/index.ts
index f2b3e62577e4f1..e640397e3936d8 100644
--- a/x-pack/plugins/search_indices/common/index.ts
+++ b/x-pack/plugins/search_indices/common/index.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
+import type { SearchIndices, SearchStart } from '@kbn/deeplinks-search/deep_links';
+
export const PLUGIN_ID = 'searchIndices';
export const PLUGIN_NAME = 'searchIndices';
+export const START_APP_ID: SearchStart = 'elasticsearchStart';
+export const INDICES_APP_ID: SearchIndices = 'elasticsearchIndices';
export type { IndicesStatusResponse, UserStartPrivilegesResponse } from './types';
diff --git a/x-pack/plugins/search_indices/public/components/start/create_index.tsx b/x-pack/plugins/search_indices/public/components/start/create_index.tsx
index bd80922d796893..f1392b3d338136 100644
--- a/x-pack/plugins/search_indices/public/components/start/create_index.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/create_index.tsx
@@ -56,13 +56,17 @@ export const CreateIndexForm = ({
const [indexNameHasError, setIndexNameHasError] = useState(false);
const usageTracker = useUsageTracker();
const { createIndex, isLoading } = useCreateIndex();
- const onCreateIndex = useCallback(() => {
- if (!isValidIndexName(formState.indexName)) {
- return;
- }
- usageTracker.click(AnalyticsEvents.startCreateIndexClick);
- createIndex({ indexName: formState.indexName });
- }, [usageTracker, createIndex, formState.indexName]);
+ const onCreateIndex = useCallback(
+ (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!isValidIndexName(formState.indexName)) {
+ return;
+ }
+ usageTracker.click(AnalyticsEvents.startCreateIndexClick);
+ createIndex({ indexName: formState.indexName });
+ },
+ [usageTracker, createIndex, formState.indexName]
+ );
const onIndexNameChange = (e: React.ChangeEvent) => {
const newIndexName = e.target.value;
setFormState({ ...formState, indexName: e.target.value });
@@ -78,7 +82,12 @@ export const CreateIndexForm = ({
return (
<>
-
+
{CREATE_INDEX_CONTENT}
@@ -181,11 +188,7 @@ export const CreateIndexForm = ({
defaultMessage="Already have some data? {link}"
values={{
link: (
-
+
{i18n.translate(
'xpack.searchIndices.startPage.createIndex.fileUpload.link',
{
diff --git a/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx b/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx
index 00bc11674684a7..8bc8d5fcd7ea26 100644
--- a/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/elasticsearch_start.tsx
@@ -221,7 +221,6 @@ export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps)
iconSide="right"
iconType="popout"
data-test-subj="analyzeLogsBtn"
- data-telemetry-id="searchIndicesStartCollectLogsLink"
href={docLinks.analyzeLogs}
target="_blank"
>
@@ -249,7 +248,6 @@ export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps)
iconSide="right"
iconType="popout"
data-test-subj="startO11yTrialBtn"
- data-telemetry-id="searchIndicesStartO11yTrialLink"
href={o11yTrialLink}
target="_blank"
>
diff --git a/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx b/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx
index d7a24a6e102d82..86fdf75a1d0800 100644
--- a/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx
+++ b/x-pack/plugins/search_indices/public/components/start/hooks/use_indices_redirect.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
import type { IndicesStatusResponse } from '../../../../common';
@@ -15,13 +15,30 @@ import { navigateToIndexDetails } from './utils';
export const useIndicesRedirect = (indicesStatus?: IndicesStatusResponse) => {
const { application, http } = useKibana().services;
+ const [lastStatus, setLastStatus] = useState(() => undefined);
+ const [hasDoneRedirect, setHasDoneRedirect] = useState(() => false);
return useEffect(() => {
- if (!indicesStatus) return;
- if (indicesStatus.indexNames.length === 0) return;
+ if (hasDoneRedirect) {
+ return;
+ }
+ if (!indicesStatus) {
+ return;
+ }
+ if (indicesStatus.indexNames.length === 0) {
+ setLastStatus(indicesStatus);
+ return;
+ }
+ if (lastStatus === undefined && indicesStatus.indexNames.length > 0) {
+ application.navigateToApp('management', { deepLinkId: 'index_management' });
+ setHasDoneRedirect(true);
+ return;
+ }
if (indicesStatus.indexNames.length === 1) {
navigateToIndexDetails(application, http, indicesStatus.indexNames[0]);
+ setHasDoneRedirect(true);
return;
}
application.navigateToApp('management', { deepLinkId: 'index_management' });
- }, [application, http, indicesStatus]);
+ setHasDoneRedirect(true);
+ }, [application, http, indicesStatus, lastStatus, hasDoneRedirect]);
};
diff --git a/x-pack/plugins/search_indices/public/plugin.ts b/x-pack/plugins/search_indices/public/plugin.ts
index bec4f7cb7bfe6a..39c3151cf76155 100644
--- a/x-pack/plugins/search_indices/public/plugin.ts
+++ b/x-pack/plugins/search_indices/public/plugin.ts
@@ -16,6 +16,8 @@ import type {
SearchIndicesServicesContextDeps,
} from './types';
import { initQueryClient } from './services/query_client';
+import { INDICES_APP_ID, START_APP_ID } from '../common';
+import { INDICES_APP_BASE, START_APP_BASE } from './routes';
export class SearchIndicesPlugin
implements Plugin
@@ -26,8 +28,8 @@ export class SearchIndicesPlugin
const queryClient = initQueryClient(core.notifications.toasts);
core.application.register({
- id: 'elasticsearchStart',
- appRoute: '/app/elasticsearch/start',
+ id: START_APP_ID,
+ appRoute: START_APP_BASE,
title: i18n.translate('xpack.searchIndices.elasticsearchStart.startAppTitle', {
defaultMessage: 'Elasticsearch Start',
}),
@@ -43,8 +45,8 @@ export class SearchIndicesPlugin
},
});
core.application.register({
- id: 'elasticsearchIndices',
- appRoute: '/app/elasticsearch/indices',
+ id: INDICES_APP_ID,
+ appRoute: INDICES_APP_BASE,
title: i18n.translate('xpack.searchIndices.elasticsearchIndices.startAppTitle', {
defaultMessage: 'Elasticsearch Indices',
}),
@@ -62,12 +64,18 @@ export class SearchIndicesPlugin
return {
enabled: true,
+ startAppId: START_APP_ID,
+ startRoute: START_APP_BASE,
};
}
public start(core: CoreStart): SearchIndicesPluginStart {
docLinks.setDocLinks(core.docLinks.links);
- return {};
+ return {
+ enabled: true,
+ startAppId: START_APP_ID,
+ startRoute: START_APP_BASE,
+ };
}
public stop() {}
diff --git a/x-pack/plugins/search_indices/public/routes.ts b/x-pack/plugins/search_indices/public/routes.ts
index 9afa0463855768..c72e84c66a7d0d 100644
--- a/x-pack/plugins/search_indices/public/routes.ts
+++ b/x-pack/plugins/search_indices/public/routes.ts
@@ -13,3 +13,6 @@ export enum SearchIndexDetailsTabs {
MAPPINGS = 'mappings',
SETTINGS = 'settings',
}
+
+export const START_APP_BASE = '/app/elasticsearch/start';
+export const INDICES_APP_BASE = '/app/elasticsearch/indices';
diff --git a/x-pack/plugins/search_indices/public/types.ts b/x-pack/plugins/search_indices/public/types.ts
index a3e63df2642b3c..ebefaf10cb6eb2 100644
--- a/x-pack/plugins/search_indices/public/types.ts
+++ b/x-pack/plugins/search_indices/public/types.ts
@@ -15,13 +15,20 @@ import type {
MappingProperty,
MappingPropertyBase,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
-import { IndexManagementPluginStart } from '@kbn/index-management-shared-types';
+import type { IndexManagementPluginStart } from '@kbn/index-management-shared-types';
+import type { AppDeepLinkId } from '@kbn/core-chrome-browser';
export interface SearchIndicesPluginSetup {
enabled: boolean;
+ startAppId: string;
+ startRoute: string;
+}
+
+export interface SearchIndicesPluginStart {
+ enabled: boolean;
+ startAppId: AppDeepLinkId;
+ startRoute: string;
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface SearchIndicesPluginStart {}
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
diff --git a/x-pack/plugins/search_indices/tsconfig.json b/x-pack/plugins/search_indices/tsconfig.json
index a564b87c7f6e6c..dfd73633b3c3b5 100644
--- a/x-pack/plugins/search_indices/tsconfig.json
+++ b/x-pack/plugins/search_indices/tsconfig.json
@@ -36,7 +36,9 @@
"@kbn/es-types",
"@kbn/search-api-keys-server",
"@kbn/search-api-keys-components",
- "@kbn/search-shared-ui"
+ "@kbn/search-shared-ui",
+ "@kbn/deeplinks-search",
+ "@kbn/core-chrome-browser"
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts
index 3fc084b5059d88..9a4ad2a5c51be5 100644
--- a/x-pack/plugins/serverless_search/public/navigation_tree.ts
+++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts
@@ -5,11 +5,27 @@
* 2.0.
*/
-import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser';
+import type {
+ AppDeepLinkId,
+ NavigationTreeDefinition,
+ NodeDefinition,
+} from '@kbn/core-chrome-browser';
import { i18n } from '@kbn/i18n';
import { CONNECTORS_LABEL } from '../common/i18n_string';
-export const navigationTree = (): NavigationTreeDefinition => ({
+const gettingStartedItem: NodeDefinition = {
+ id: 'gettingStarted',
+ title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
+ defaultMessage: 'Getting Started',
+ }),
+ link: 'serverlessElasticsearch',
+ spaceBefore: 'm',
+};
+
+export const navigationTree = (
+ homeLink: AppDeepLinkId = 'serverlessElasticsearch' as AppDeepLinkId,
+ showGettingStarted: boolean
+): NavigationTreeDefinition => ({
body: [
{
type: 'navGroup',
@@ -25,7 +41,7 @@ export const navigationTree = (): NavigationTreeDefinition => ({
title: i18n.translate('xpack.serverlessSearch.nav.home', {
defaultMessage: 'Home',
}),
- link: 'serverlessElasticsearch',
+ link: homeLink,
spaceBefore: 'm',
},
{
@@ -70,6 +86,16 @@ export const navigationTree = (): NavigationTreeDefinition => ({
link: 'management:index_management',
breadcrumbStatus:
'hidden' /* management sub-pages set their breadcrumbs themselves */,
+ getIsActive: ({ pathNameSerialized, prepend }) => {
+ return (
+ pathNameSerialized.startsWith(
+ prepend('/app/management/data/index_management/')
+ ) ||
+ pathNameSerialized.startsWith(
+ prepend('/app/elasticsearch/indices/index_details/')
+ )
+ );
+ },
},
{
title: CONNECTORS_LABEL,
@@ -112,6 +138,7 @@ export const navigationTree = (): NavigationTreeDefinition => ({
},
],
},
+ ...(showGettingStarted ? [gettingStartedItem] : []),
],
},
],
diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts
index 66daece706eab5..211a1cb2384d2a 100644
--- a/x-pack/plugins/serverless_search/public/plugin.ts
+++ b/x-pack/plugins/serverless_search/public/plugin.ts
@@ -73,6 +73,10 @@ export class ServerlessSearchPlugin
const homeTitle = i18n.translate('xpack.serverlessSearch.app.home.title', {
defaultMessage: 'Home',
});
+ const useGlobalEmptyState = setupDeps.searchIndices?.enabled ?? false;
+ const serverlessElasticsearchAppRoute = useGlobalEmptyState
+ ? '/app/elasticsearch/getting_started'
+ : '/app/elasticsearch';
core.application.register({
id: 'serverlessElasticsearch',
@@ -81,7 +85,7 @@ export class ServerlessSearchPlugin
}),
euiIconType: 'logoElastic',
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
- appRoute: '/app/elasticsearch',
+ appRoute: serverlessElasticsearchAppRoute,
async mount({ element, history }: AppMountParameters) {
const { renderApp } = await import('./application/elasticsearch');
const [coreStart, services] = await core.getStartServices();
@@ -120,6 +124,24 @@ export class ServerlessSearchPlugin
},
});
+ if (useGlobalEmptyState) {
+ const redirectApp = setupDeps.searchIndices!.startAppId;
+ core.application.register({
+ id: 'serverlessHomeRedirect',
+ title: homeTitle,
+ appRoute: '/app/elasticsearch',
+ euiIconType: 'logoElastic',
+ category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
+ visibleIn: [],
+ async mount({}: AppMountParameters) {
+ const [coreStart] = await core.getStartServices();
+ coreStart.chrome.docTitle.change(homeTitle);
+ coreStart.application.navigateToApp(redirectApp);
+ return () => {};
+ },
+ });
+ }
+
setupDeps.discover.showInlineTopNav();
return {};
@@ -130,10 +152,15 @@ export class ServerlessSearchPlugin
services: ServerlessSearchPluginStartDependencies
): ServerlessSearchPluginStart {
const { serverless, management, indexManagement, security } = services;
-
- serverless.setProjectHome('/app/elasticsearch');
-
- const navigationTree$ = of(navigationTree());
+ const useGlobalEmptyState = services.searchIndices?.enabled ?? false;
+ const homeRoute = useGlobalEmptyState
+ ? services.searchIndices!.startRoute
+ : '/app/elasticsearch';
+ serverless.setProjectHome(homeRoute);
+
+ const navigationTree$ = of(
+ navigationTree(services.searchIndices?.startAppId, useGlobalEmptyState)
+ );
serverless.initNavigation('search', navigationTree$, { dataTestSubj: 'svlSearchSideNav' });
const extendCardNavDefinitions = serverless.getNavigationCards(
diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts
index 7067851bc01967..65952c963a2a45 100644
--- a/x-pack/plugins/serverless_search/public/types.ts
+++ b/x-pack/plugins/serverless_search/public/types.ts
@@ -15,6 +15,10 @@ import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverle
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { IndexManagementPluginStart } from '@kbn/index-management-plugin/public';
import type { DiscoverSetup } from '@kbn/discover-plugin/public';
+import type {
+ SearchIndicesPluginSetup,
+ SearchIndicesPluginStart,
+} from '@kbn/search-indices/public';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ServerlessSearchPluginSetup {}
@@ -27,6 +31,7 @@ export interface ServerlessSearchPluginSetupDependencies {
management: ManagementSetup;
serverless: ServerlessPluginSetup;
discover: DiscoverSetup;
+ searchIndices?: SearchIndicesPluginSetup;
}
export interface ServerlessSearchPluginStartDependencies {
@@ -39,4 +44,5 @@ export interface ServerlessSearchPluginStartDependencies {
serverless: ServerlessPluginStart;
share: SharePluginStart;
indexManagement?: IndexManagementPluginStart;
+ searchIndices?: SearchIndicesPluginStart;
}
diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json
index c856b155acc7cc..cc3b7b073dcee0 100644
--- a/x-pack/plugins/serverless_search/tsconfig.json
+++ b/x-pack/plugins/serverless_search/tsconfig.json
@@ -51,5 +51,6 @@
"@kbn/security-api-key-management",
"@kbn/search-inference-endpoints",
"@kbn/security-plugin-types-common",
+ "@kbn/search-indices",
]
}
diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts
index 826b8bce038855..963be692d58ea4 100644
--- a/x-pack/test_serverless/functional/config.base.ts
+++ b/x-pack/test_serverless/functional/config.base.ts
@@ -116,6 +116,7 @@ export function createTestConfig(options: CreateTestConfigOptions) {
integrations: {
pathname: '/app/integrations',
},
+ ...(options.apps ?? {}),
},
// choose where screenshots should be saved
screenshots: {
diff --git a/x-pack/test_serverless/functional/services/svl_search_navigation.ts b/x-pack/test_serverless/functional/services/svl_search_navigation.ts
index 9de765cabd6bb7..cf91aac1127738 100644
--- a/x-pack/test_serverless/functional/services/svl_search_navigation.ts
+++ b/x-pack/test_serverless/functional/services/svl_search_navigation.ts
@@ -22,12 +22,20 @@ export function SvlSearchNavigationServiceProvider({
await testSubjects.existOrFail('svlSearchOverviewPage', { timeout: 2000 });
});
},
- async navigateToElasticsearchStartPage() {
+ async navigateToGettingStartedPage() {
await retry.tryForTime(60 * 1000, async () => {
- await PageObjects.common.navigateToApp('elasticsearch/start', {
+ await PageObjects.common.navigateToApp('serverlessElasticsearch');
+ await testSubjects.existOrFail('svlSearchOverviewPage', { timeout: 2000 });
+ });
+ },
+ async navigateToElasticsearchStartPage(expectRedirect: boolean = false) {
+ await retry.tryForTime(60 * 1000, async () => {
+ await PageObjects.common.navigateToApp('elasticsearchStart', {
shouldLoginIfPrompted: false,
});
- await testSubjects.existOrFail('elasticsearchStartPage', { timeout: 2000 });
+ if (!expectRedirect) {
+ await testSubjects.existOrFail('elasticsearchStartPage', { timeout: 2000 });
+ }
});
},
async navigateToIndexDetailPage(indexName: string) {
diff --git a/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts
index 824d145282257b..ebd539fd34f42f 100644
--- a/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts
@@ -34,4 +34,15 @@ export default createTestConfig({
// include settings from project controller
// https://github.com/elastic/project-controller/blob/main/internal/project/esproject/config/elasticsearch.yml
esServerArgs: ['xpack.security.authc.native_roles.enabled=true'],
+ apps: {
+ serverlessElasticsearch: {
+ pathname: '/app/elasticsearch/getting_started',
+ },
+ elasticsearchStart: {
+ pathname: '/app/elasticsearch/start',
+ },
+ elasticsearchIndices: {
+ pathname: '/app/elasticsearch/indices',
+ },
+ },
});
diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts
index 4739cde53bf860..678adecd4bff56 100644
--- a/x-pack/test_serverless/functional/test_suites/search/config.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/config.ts
@@ -24,4 +24,15 @@ export default createTestConfig({
`--xpack.cloud.base_url=https://fake-cloud.elastic.co`,
`--xpack.cloud.projects_url=/projects/`,
],
+ apps: {
+ serverlessElasticsearch: {
+ pathname: '/app/elasticsearch',
+ },
+ serverlessConnectors: {
+ pathname: '/app/connectors',
+ },
+ searchPlayground: {
+ pathname: '/app/search_playground',
+ },
+ },
});
diff --git a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
index 9a4b72cf5126f5..d9d4389d4d63c0 100644
--- a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts
@@ -26,8 +26,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await esDeleteAllIndices(['search-*', 'test-*']);
};
- // Failing: See https://github.com/elastic/kibana/issues/194673
- describe.skip('Elasticsearch Start [Onboarding Empty State]', function () {
+ describe('Elasticsearch Start [Onboarding Empty State]', function () {
describe('developer', function () {
before(async () => {
await pageObjects.svlCommonPage.loginWithRole('developer');
@@ -77,6 +76,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await es.indices.create({ index: 'test-my-index-002' });
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexListPage();
});
+ it('should redirect to indices list if single index exist on page load', async () => {
+ await svlSearchNavigation.navigateToGettingStartedPage();
+ await es.indices.create({ index: 'test-my-index-001' });
+ await svlSearchNavigation.navigateToElasticsearchStartPage(true);
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexListPage();
+ });
it('should support switching between UI and Code Views', async () => {
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
@@ -87,7 +92,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexUIView();
});
- it('should show the api key in code view', async () => {
+ // Failing: See https://github.com/elastic/kibana/issues/194673
+ it.skip('should show the api key in code view', async () => {
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
await pageObjects.svlSearchElasticsearchStartPage.clickCodeViewButton();
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
@@ -125,7 +131,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
});
- it('Same API Key should be present on start page and index detail view', async () => {
+ // Failing: See https://github.com/elastic/kibana/issues/194673
+ it.skip('Same API Key should be present on start page and index detail view', async () => {
await pageObjects.svlSearchElasticsearchStartPage.clickCodeViewButton();
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
const apiKeyUI = await pageObjects.svlApiKeys.getAPIKeyFromUI();
@@ -151,41 +158,40 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlSearchElasticsearchStartPage.expectAnalyzeLogsLink();
await pageObjects.svlSearchElasticsearchStartPage.expectO11yTrialLink();
});
+ });
+ describe('viewer', function () {
+ before(async () => {
+ await pageObjects.svlCommonPage.loginAsViewer();
+ await deleteAllTestIndices();
+ });
+ beforeEach(async () => {
+ await svlSearchNavigation.navigateToElasticsearchStartPage();
+ });
+ after(async () => {
+ await deleteAllTestIndices();
+ });
+
+ it('should default to code view when lacking create index permissions', async () => {
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
+ await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexCodeView();
+ await pageObjects.svlSearchElasticsearchStartPage.clickUIViewButton();
+ await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexUIView();
+ await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexButtonToBeDisabled();
+ });
+
+ it('should not create an API key if the user only has viewer permissions', async () => {
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
+ await pageObjects.svlSearchElasticsearchStartPage.clickCodeViewButton();
+ await pageObjects.svlSearchElasticsearchStartPage.expectAPIKeyFormNotAvailable();
+ const apiKey = await pageObjects.svlApiKeys.getAPIKeyFromSessionStorage();
+ expect(apiKey).to.be(null);
+ });
- describe('viewer', function () {
- before(async () => {
- await pageObjects.svlCommonPage.loginAsViewer();
- await deleteAllTestIndices();
- });
- beforeEach(async () => {
- await svlSearchNavigation.navigateToElasticsearchStartPage();
- });
- after(async () => {
- await deleteAllTestIndices();
- });
-
- it('should default to code view when lacking create index permissions', async () => {
- await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
- await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexCodeView();
- await pageObjects.svlSearchElasticsearchStartPage.clickUIViewButton();
- await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexUIView();
- await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexButtonToBeDisabled();
- });
-
- it('should not create an API key if the user only has viewer permissions', async () => {
- await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
- await pageObjects.svlSearchElasticsearchStartPage.clickCodeViewButton();
- await pageObjects.svlSearchElasticsearchStartPage.expectAPIKeyFormNotAvailable();
- const apiKey = await pageObjects.svlApiKeys.getAPIKeyFromSessionStorage();
- expect(apiKey).to.be(null);
- });
-
- it('should redirect to index details when index is created via API', async () => {
- await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
- await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexCodeView();
- await es.indices.create({ index: 'test-my-api-index' });
- await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexDetailsPage();
- });
+ it('should redirect to index details when index is created via API', async () => {
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
+ await pageObjects.svlSearchElasticsearchStartPage.expectCreateIndexCodeView();
+ await es.indices.create({ index: 'test-my-api-index' });
+ await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexDetailsPage();
});
});
});
diff --git a/x-pack/test_serverless/functional/test_suites/search/landing_page.ts b/x-pack/test_serverless/functional/test_suites/search/getting_started.ts
similarity index 95%
rename from x-pack/test_serverless/functional/test_suites/search/landing_page.ts
rename to x-pack/test_serverless/functional/test_suites/search/getting_started.ts
index 643d163bf76722..f521a03ccde850 100644
--- a/x-pack/test_serverless/functional/test_suites/search/landing_page.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/getting_started.ts
@@ -13,13 +13,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects(['svlSearchLandingPage', 'svlCommonPage', 'embeddedConsole']);
const svlSearchNavigation = getService('svlSearchNavigation');
- describe('landing page', function () {
+ describe('getting started', function () {
before(async () => {
await pageObjects.svlCommonPage.loginAsViewer();
});
it('has serverless side nav', async () => {
- await svlSearchNavigation.navigateToLandingPage();
+ await svlSearchNavigation.navigateToGettingStartedPage();
await pageObjects.svlSearchLandingPage.assertSvlSearchSideNavExists();
});
@@ -44,7 +44,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('API Key creation', () => {
beforeEach(async () => {
// We need to reload the page between api key creations
- await svlSearchNavigation.navigateToLandingPage();
+ await svlSearchNavigation.navigateToGettingStartedPage();
});
it('can create an API key that expires', async () => {
@@ -86,7 +86,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
describe('Pipelines', () => {
beforeEach(async () => {
- await svlSearchNavigation.navigateToLandingPage();
+ await svlSearchNavigation.navigateToGettingStartedPage();
});
it('can redirect to the pipeline creation index page', async () => {
await pageObjects.svlSearchLandingPage.pipeline.createPipeline();
diff --git a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts
index c5c9b82dd21cd5..db9df2e8d913cb 100644
--- a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts
@@ -12,6 +12,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
// add tests that require feature flags, defined in config.feature_flags.ts
loadTestFile(require.resolve('./elasticsearch_start.ts'));
loadTestFile(require.resolve('./search_index_detail.ts'));
+ loadTestFile(require.resolve('./getting_started'));
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
loadTestFile(require.resolve('../common/platform_security/roles.ts'));
loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts'));
diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts
index bc2056268f8bd7..250f99d13a3a12 100644
--- a/x-pack/test_serverless/functional/test_suites/search/index.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/index.ts
@@ -11,7 +11,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('serverless search UI', function () {
this.tags(['esGate']);
- loadTestFile(require.resolve('./landing_page'));
+ loadTestFile(require.resolve('./getting_started'));
loadTestFile(require.resolve('./connectors/connectors_overview'));
loadTestFile(require.resolve('./default_dataview'));
loadTestFile(require.resolve('./navigation'));
diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
index db855e9522f9a1..0f1ee8334e2038 100644
--- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
+++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts
@@ -84,7 +84,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.embeddedConsole.clickEmbeddedConsoleControlBar();
});
- it('should show api key', async () => {
+ // Failing: See https://github.com/elastic/kibana/issues/194673
+ it.skip('should show api key', async () => {
await pageObjects.svlApiKeys.expectAPIKeyAvailable();
const apiKey = await pageObjects.svlApiKeys.getAPIKeyFromUI();
await pageObjects.svlSearchIndexDetailPage.expectAPIKeyToBeVisibleInCodeBlock(apiKey);
diff --git a/x-pack/test_serverless/shared/types/index.ts b/x-pack/test_serverless/shared/types/index.ts
index e07c649d86cb9c..2545bc0d30539b 100644
--- a/x-pack/test_serverless/shared/types/index.ts
+++ b/x-pack/test_serverless/shared/types/index.ts
@@ -16,4 +16,5 @@ export interface CreateTestConfigOptions {
junit: { reportName: string };
suiteTags?: { include?: string[]; exclude?: string[] };
services?: InheritedServices;
+ apps?: Record;
}
From 14d5677d073838845074f76f0d9c0edce50533f0 Mon Sep 17 00:00:00 2001
From: Cristina Amico
Date: Tue, 8 Oct 2024 16:36:19 +0200
Subject: [PATCH 09/50] [Fleet] Adjust privileges for GET output and GET
download_source endpoints (#194951)
Fixes https://github.com/elastic/kibana/issues/191266
## Summary
Updating the authz for following endpoints:
- `GET /agent_download_sources`
- `GET /agent_download_sources/{id}`
- `GET /outputs`
- `GET /outputs/{id}`
They need to have `authz.fleet.readSettings ||
authz.fleet.readAgentPolicies` as they should be visible in the agent
policy settings page as well.
### Testing
- Enable feature flag `subfeaturePrivileges`
- Create a role with following privileges:
![Screenshot 2024-10-04 at 15 49
54](https://github.com/user-attachments/assets/4bbc95e4-01d0-43e0-a539-b03b8f4c219e)
- Create a user that has the previous role
- Log in and go to any agent policy > settings
- The download source and output fields should be filled and editable.
They were previously empty, as the GET endpoints were failing with 403
Forbidden
### Checklist
- [ ] [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: Elastic Machine
---
.../server/routes/download_source/index.ts | 8 +--
.../fleet/server/routes/output/index.ts | 12 ++---
.../apis/fleet_settings_privileges.ts | 51 +++++++++++++++++--
3 files changed, 57 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/fleet/server/routes/download_source/index.ts b/x-pack/plugins/fleet/server/routes/download_source/index.ts
index cf2a5b19c4b473..83059593730db2 100644
--- a/x-pack/plugins/fleet/server/routes/download_source/index.ts
+++ b/x-pack/plugins/fleet/server/routes/download_source/index.ts
@@ -36,8 +36,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.versioned
.get({
path: DOWNLOAD_SOURCE_API_ROUTES.LIST_PATTERN,
- fleetAuthz: {
- fleet: { readSettings: true },
+ fleetAuthz: (authz) => {
+ return authz.fleet.readSettings || authz.fleet.readAgentPolicies;
},
description: `List agent binary download sources`,
options: {
@@ -65,8 +65,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.versioned
.get({
path: DOWNLOAD_SOURCE_API_ROUTES.INFO_PATTERN,
- fleetAuthz: {
- fleet: { readSettings: true },
+ fleetAuthz: (authz) => {
+ return authz.fleet.readSettings || authz.fleet.readAgentPolicies;
},
description: `Get agent binary download source by ID`,
options: {
diff --git a/x-pack/plugins/fleet/server/routes/output/index.ts b/x-pack/plugins/fleet/server/routes/output/index.ts
index b222f9f737d1df..a90735f053208e 100644
--- a/x-pack/plugins/fleet/server/routes/output/index.ts
+++ b/x-pack/plugins/fleet/server/routes/output/index.ts
@@ -40,8 +40,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.versioned
.get({
path: OUTPUT_API_ROUTES.LIST_PATTERN,
- fleetAuthz: {
- fleet: { readSettings: true },
+ fleetAuthz: (authz) => {
+ return authz.fleet.readSettings || authz.fleet.readAgentPolicies;
},
description: 'List outputs',
options: {
@@ -68,8 +68,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.versioned
.get({
path: OUTPUT_API_ROUTES.INFO_PATTERN,
- fleetAuthz: {
- fleet: { readSettings: true },
+ fleetAuthz: (authz) => {
+ return authz.fleet.readSettings || authz.fleet.readAgentPolicies;
},
description: 'Get output by ID',
options: {
@@ -96,8 +96,8 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
router.versioned
.put({
path: OUTPUT_API_ROUTES.UPDATE_PATTERN,
- fleetAuthz: {
- fleet: { allSettings: true },
+ fleetAuthz: (authz) => {
+ return authz.fleet.allSettings || authz.fleet.allAgentPolicies;
},
description: 'Update output by ID',
options: {
diff --git a/x-pack/test/fleet_api_integration/apis/fleet_settings_privileges.ts b/x-pack/test/fleet_api_integration/apis/fleet_settings_privileges.ts
index 12399d5ba9bf2a..7d6a58c0661212 100644
--- a/x-pack/test/fleet_api_integration/apis/fleet_settings_privileges.ts
+++ b/x-pack/test/fleet_api_integration/apis/fleet_settings_privileges.ts
@@ -51,6 +51,49 @@ const READ_SCENARIOS = [
statusCode: 403,
},
];
+// Scenarios updated for download_source and outputs routes that are slightly different
+const READ_SCENARIOS_2 = [
+ {
+ user: testUsers.fleet_all_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_read_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_settings_all_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_settings_read_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_agent_policies_read_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_agent_policies_all_only,
+ statusCode: 200,
+ },
+ {
+ user: testUsers.fleet_agents_read_only,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_no_access,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_minimal_all_only,
+ statusCode: 403,
+ },
+ {
+ user: testUsers.fleet_minimal_read_only,
+ statusCode: 403,
+ },
+];
const ALL_SCENARIOS = [
{
@@ -106,12 +149,12 @@ export default function (providerContext: FtrProviderContext) {
{
method: 'GET',
path: '/api/fleet/outputs',
- scenarios: READ_SCENARIOS,
+ scenarios: READ_SCENARIOS_2,
},
{
method: 'GET',
path: '/api/fleet/outputs/test-privileges-output-1',
- scenarios: READ_SCENARIOS,
+ scenarios: READ_SCENARIOS_2,
},
{
method: 'POST',
@@ -226,12 +269,12 @@ export default function (providerContext: FtrProviderContext) {
{
method: 'GET',
path: '/api/fleet/agent_download_sources',
- scenarios: READ_SCENARIOS,
+ scenarios: READ_SCENARIOS_2,
},
{
method: 'GET',
path: '/api/fleet/agent_download_sources/test-privileges-download-source-1',
- scenarios: READ_SCENARIOS,
+ scenarios: READ_SCENARIOS_2,
},
{
method: 'POST',
From da88bb2c01c7c2748ac60e233afd687b9ed32cf2 Mon Sep 17 00:00:00 2001
From: Kyle Pollich
Date: Tue, 8 Oct 2024 10:37:04 -0400
Subject: [PATCH 10/50] [Fleet] Update GitHub action to route Fleet issues to
new project (#195417)
## Summary
The UI & Integrations team is moving to a new GitHub project. This PR
updates our automation to automatically place issues in the new project
instead of the old one.
---
.github/workflows/add-fleet-issues-to-ingest-project.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/add-fleet-issues-to-ingest-project.yml b/.github/workflows/add-fleet-issues-to-ingest-project.yml
index ca04c0e6fa6d74..86c42d95ed403c 100644
--- a/.github/workflows/add-fleet-issues-to-ingest-project.yml
+++ b/.github/workflows/add-fleet-issues-to-ingest-project.yml
@@ -1,4 +1,4 @@
-name: Add Fleet issue to Platform Ingest project
+name: Add Fleet issue to Platform Ingest - UI & Integrations project
on:
issues:
@@ -6,9 +6,9 @@ on:
- labeled
env:
- INGEST_PROJECT_ID: 'PVT_kwDOAGc3Zs4AEzn4'
+ INGEST_PROJECT_ID: 'PVT_kwDOAGc3Zs4AogrP'
FLEET_LABEL: 'Team:Fleet'
- AREA_FIELD_ID: 'PVTSSF_lADOAGc3Zs4AEzn4zgEgZSo'
+ AREA_FIELD_ID: 'PVTSSF_lADOAGc3Zs4AogrPzggEiBs'
FLEET_UI_OPTION_ID: '411a7b86'
jobs:
From 560d561e21fe51020954bd9e3246f238ffa026ba Mon Sep 17 00:00:00 2001
From: Justin Kambic
Date: Tue, 8 Oct 2024 10:46:30 -0400
Subject: [PATCH 11/50] [Synthetics] Fix ping heatmap payload (#195107)
## Summary
We addressed https://github.com/elastic/kibana/issues/180076 recently
with these two PRs:
- https://github.com/elastic/kibana/pull/184177
- https://github.com/elastic/kibana/pull/192508
We were seeing a strange error that was difficult to repro, so we put in
a best-effort patch that was still ineffective. The reason this issue
happens is because in the code it's possible to divide by 0, which
yields a value of `Infinity`, which at some point causes our interval
value supplied to the server route to be an empty string.
This patch will make it so that we never pass a value of 0 to be used in
the calculation of bucket sizes in this hook.
---
.../monitor_details/monitor_status/use_monitor_status_data.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts
index efb001f1776b7f..8eaa80fb44a53d 100644
--- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts
+++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/use_monitor_status_data.ts
@@ -93,7 +93,7 @@ export const useMonitorStatusData = ({ from, to, initialSizeRef }: Props) => {
useDebounce(
async () => {
- setDebouncedCount(binsAvailableByWidth);
+ setDebouncedCount(binsAvailableByWidth === 0 ? null : binsAvailableByWidth);
},
500,
[binsAvailableByWidth]
From 149e801109d7c9edf2d6eef41ebb0e281314a19f Mon Sep 17 00:00:00 2001
From: Stratoula Kalafateli
Date: Tue, 8 Oct 2024 16:52:21 +0200
Subject: [PATCH 12/50] [ES|QL] Recommended queries (#194418)
## Summary
Closes https://github.com/elastic/kibana/issues/187325
This is the first iteration of this feature. We want to help the users
familiarize themselves with popular operations. This PR:
- adds the recommended queries list in the help menu of unified search
- adds the list after the users select a datasource with the from
command
- adds the list in the editor's empty state
### Checklist
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../kbn-esql-validation-autocomplete/index.ts | 2 +
.../autocomplete.command.from.test.ts | 12 +-
.../__tests__/autocomplete.suggest.test.ts | 5 +-
.../src/autocomplete/autocomplete.test.ts | 57 ++++--
.../src/autocomplete/autocomplete.ts | 49 ++++-
.../src/autocomplete/helper.ts | 4 +-
.../recommended_queries/suggestions.ts | 40 ++++
.../recommended_queries/templates.ts | 129 +++++++++++++
.../src/autocomplete/types.ts | 6 +
.../src/definitions/commands.ts | 1 +
.../src/definitions/types.ts | 1 +
.../esql_menu_popover.test.tsx | 14 +-
.../query_string_input/esql_menu_popover.tsx | 179 ++++++++++++------
.../query_string_input/query_bar_top_row.tsx | 7 +
.../public/search_bar/search_bar.test.tsx | 39 ++--
src/plugins/unified_search/tsconfig.json | 1 +
16 files changed, 424 insertions(+), 122 deletions(-)
create mode 100644 packages/kbn-esql-validation-autocomplete/src/autocomplete/recommended_queries/suggestions.ts
create mode 100644 packages/kbn-esql-validation-autocomplete/src/autocomplete/recommended_queries/templates.ts
diff --git a/packages/kbn-esql-validation-autocomplete/index.ts b/packages/kbn-esql-validation-autocomplete/index.ts
index bd41d6ec43a5ad..0bfc4274fe84de 100644
--- a/packages/kbn-esql-validation-autocomplete/index.ts
+++ b/packages/kbn-esql-validation-autocomplete/index.ts
@@ -76,3 +76,5 @@ export {
} from './src/shared/resources_helpers';
export { wrapAsEditorMessage } from './src/code_actions/utils';
+
+export { getRecommendedQueries } from './src/autocomplete/recommended_queries/templates';
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts
index fa2a969384a09c..fa2e81ded897e6 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts
@@ -9,6 +9,7 @@
import { METADATA_FIELDS } from '../../shared/constants';
import { setup, indexes, integrations } from './helpers';
+import { getRecommendedQueries } from '../recommended_queries/templates';
const visibleIndices = indexes
.filter(({ hidden }) => !hidden)
@@ -72,8 +73,17 @@ describe('autocomplete.suggest', () => {
const metadataFieldsAndIndex = metadataFields.filter((field) => field !== '_index');
test('on SPACE without comma ",", suggests adding metadata', async () => {
+ const recommendedQueries = getRecommendedQueries({
+ fromCommand: '',
+ timeField: 'dateField',
+ });
const { assertSuggestions } = await setup();
- const expected = ['METADATA $0', ',', '| '].sort();
+ const expected = [
+ 'METADATA $0',
+ ',',
+ '| ',
+ ...recommendedQueries.map((query) => query.queryString),
+ ].sort();
await assertSuggestions('from a, b /', expected);
});
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.test.ts
index c4803e769c1fc0..51302d0d4cde55 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.test.ts
@@ -36,12 +36,9 @@ const setup = async (caret = '?') => {
};
describe('autocomplete.suggest', () => {
- test('does not load fields when suggesting within a single FROM, SHOW, ROW command', async () => {
+ test('does not load fields when suggesting within a single SHOW, ROW command', async () => {
const { suggest, callbacks } = await setup();
- await suggest('FROM kib, ? |');
- await suggest('FROM ?');
- await suggest('FROM ? |');
await suggest('sHoW ?');
await suggest('row ? |');
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
index 095cb2ffc9d145..84779f1dd36b5e 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts
@@ -35,8 +35,16 @@ import {
import { METADATA_FIELDS } from '../shared/constants';
import { ESQL_COMMON_NUMERIC_TYPES, ESQL_STRING_TYPES } from '../shared/esql_types';
import { log10ParameterTypes, powParameterTypes } from './__tests__/constants';
+import { getRecommendedQueries } from './recommended_queries/templates';
const commandDefinitions = unmodifiedCommandDefinitions.filter(({ hidden }) => !hidden);
+
+const getRecommendedQueriesSuggestions = (fromCommand: string, timeField?: string) =>
+ getRecommendedQueries({
+ fromCommand,
+ timeField,
+ });
+
describe('autocomplete', () => {
type TestArgs = [
string,
@@ -82,10 +90,11 @@ describe('autocomplete', () => {
const sourceCommands = ['row', 'from', 'show'];
describe('New command', () => {
- testSuggestions(
- '/',
- sourceCommands.map((name) => name.toUpperCase() + ' $0')
- );
+ const recommendedQuerySuggestions = getRecommendedQueriesSuggestions('FROM logs*', 'dateField');
+ testSuggestions('/', [
+ ...sourceCommands.map((name) => name.toUpperCase() + ' $0'),
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
+ ]);
testSuggestions(
'from a | /',
commandDefinitions
@@ -523,10 +532,11 @@ describe('autocomplete', () => {
*/
describe('Invoke trigger kind (all commands)', () => {
// source command
- testSuggestions(
- 'f/',
- sourceCommands.map((cmd) => `${cmd.toUpperCase()} $0`)
- );
+ let recommendedQuerySuggestions = getRecommendedQueriesSuggestions('FROM logs*', 'dateField');
+ testSuggestions('f/', [
+ ...sourceCommands.map((cmd) => `${cmd.toUpperCase()} $0`),
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
+ ]);
// pipe command
testSuggestions(
@@ -575,7 +585,13 @@ describe('autocomplete', () => {
]);
// FROM source METADATA
- testSuggestions('FROM index1 M/', [',', 'METADATA $0', '| ']);
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('', 'dateField');
+ testSuggestions('FROM index1 M/', [
+ ',',
+ 'METADATA $0',
+ '| ',
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
+ ]);
// FROM source METADATA field
testSuggestions('FROM index1 METADATA _/', METADATA_FIELDS);
@@ -710,12 +726,12 @@ describe('autocomplete', () => {
...s,
asSnippet: true,
});
-
+ let recommendedQuerySuggestions = getRecommendedQueriesSuggestions('FROM logs*', 'dateField');
// Source command
- testSuggestions(
- 'F/',
- ['FROM $0', 'ROW $0', 'SHOW $0'].map(attachTriggerCommand).map(attachAsSnippet)
- );
+ testSuggestions('F/', [
+ ...['FROM $0', 'ROW $0', 'SHOW $0'].map(attachTriggerCommand).map(attachAsSnippet),
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
+ ]);
// Pipe command
testSuggestions(
@@ -787,11 +803,14 @@ describe('autocomplete', () => {
);
});
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('', 'dateField');
+
// PIPE (|)
testSuggestions('FROM a /', [
attachTriggerCommand('| '),
',',
attachAsSnippet(attachTriggerCommand('METADATA $0')),
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
]);
// Assignment
@@ -833,6 +852,7 @@ describe('autocomplete', () => {
],
]
);
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('index1', 'dateField');
testSuggestions(
'FROM index1/',
@@ -840,6 +860,7 @@ describe('autocomplete', () => {
{ text: 'index1 | ', filterText: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
{ text: 'index1, ', filterText: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
{ text: 'index1 METADATA ', filterText: 'index1', command: TRIGGER_SUGGESTION_COMMAND },
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
],
undefined,
[
@@ -851,12 +872,14 @@ describe('autocomplete', () => {
]
);
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('index2', 'dateField');
testSuggestions(
'FROM index1, index2/',
[
{ text: 'index2 | ', filterText: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
{ text: 'index2, ', filterText: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
{ text: 'index2 METADATA ', filterText: 'index2', command: TRIGGER_SUGGESTION_COMMAND },
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
],
undefined,
[
@@ -872,6 +895,7 @@ describe('autocomplete', () => {
// meaning that Monaco by default will only set the replacement
// range to cover "bar" and not "foo$bar". We have to make sure
// we're setting it ourselves.
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('foo$bar', 'dateField');
testSuggestions(
'FROM foo$bar/',
[
@@ -894,18 +918,21 @@ describe('autocomplete', () => {
command: TRIGGER_SUGGESTION_COMMAND,
rangeToReplace: { start: 6, end: 13 },
},
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
],
undefined,
[, [{ name: 'foo$bar', hidden: false }]]
);
// This is an identifier that matches multiple sources
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('i*', 'dateField');
testSuggestions(
'FROM i*/',
[
{ text: 'i* | ', filterText: 'i*', command: TRIGGER_SUGGESTION_COMMAND },
{ text: 'i*, ', filterText: 'i*', command: TRIGGER_SUGGESTION_COMMAND },
{ text: 'i* METADATA ', filterText: 'i*', command: TRIGGER_SUGGESTION_COMMAND },
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
],
undefined,
[
@@ -918,11 +945,13 @@ describe('autocomplete', () => {
);
});
+ recommendedQuerySuggestions = getRecommendedQueriesSuggestions('', 'dateField');
// FROM source METADATA
testSuggestions('FROM index1 M/', [
',',
attachAsSnippet(attachTriggerCommand('METADATA $0')),
'| ',
+ ...recommendedQuerySuggestions.map((q) => q.queryString),
]);
describe('ENRICH', () => {
diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
index 41060675a73a84..2433f5d4965217 100644
--- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
+++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts
@@ -19,7 +19,7 @@ import type {
} from '@kbn/esql-ast';
import { i18n } from '@kbn/i18n';
import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types';
-import type { EditorContext, ItemKind, SuggestionRawDefinition } from './types';
+import type { EditorContext, ItemKind, SuggestionRawDefinition, GetFieldsByTypeFn } from './types';
import {
getColumnForASTNode,
getCommandDefinition,
@@ -113,12 +113,8 @@ import {
import { metadataOption } from '../definitions/options';
import { comparisonFunctions } from '../definitions/builtin';
import { countBracketsUnclosed } from '../shared/helpers';
+import { getRecommendedQueriesSuggestions } from './recommended_queries/suggestions';
-type GetFieldsByTypeFn = (
- type: string | string[],
- ignored?: string[],
- options?: { advanceCursor?: boolean; openSuggestions?: boolean; addComma?: boolean }
-) => Promise;
type GetFieldsMapFn = () => Promise
+ ),
+ }}
+ />{' '}
+
+
+
+
+ {installStackCommand}
+
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.observability_onboarding.otelKubernetesPanel.downloadValuesFileButtonEmptyLabel',
+ { defaultMessage: 'Download values file' }
+ )}
+
+
+
+ >
+ ) : (
+
+ ),
+ },
+ {
+ title: i18n.translate(
+ 'xpack.observability_onboarding.otelKubernetesPanel.instrumentApplicationStepTitle',
+ {
+ defaultMessage: 'Instrument your application (optional)',
+ }
+ ),
+ children: (
+ <>
+
+ {i18n.translate(
+ 'xpack.observability_onboarding.otelKubernetesPanel.theOperatorAutomatesTheLabel',
+ {
+ defaultMessage:
+ 'The Operator automates the injection of auto-instrumentation libraries into the annotated pods for some languages.',
+ }
+ )}
+
+
+ setIdSelected(optionId)}
+ options={[
+ {
+ id: 'nodejs',
+ label: 'Node.js',
+ },
+ {
+ id: 'java',
+ label: 'Java',
+ },
+ {
+ id: 'python',
+ label: 'Python',
+ },
+ {
+ id: 'dotnet',
+ label: '.NET',
+ },
+ {
+ id: 'go',
+ label: 'Go',
+ },
+ ]}
+ />
+
+
+ {`apiVersion: v1
+kind: Pod
+metadata:
+ name: my-app
+ annotations:
+ instrumentation.opentelemetry.io/inject-${idSelected}: "true"
+spec:
+ containers:
+ - name: my-app
+ image: my-app:latest`}
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.observability_onboarding.otelKubernetesPanel.referToTheDocumentationLinkLabel',
+ { defaultMessage: 'refer to the documentation' }
+ )}
+
+ ),
+ }}
+ />
+
+ >
+ ),
+ },
+ {
+ title: i18n.translate(
+ 'xpack.observability_onboarding.otelKubernetesPanel.monitorStepTitle',
+ {
+ defaultMessage: 'Visualize your data',
+ }
+ ),
+ children: data ? (
+ <>
+
+ {i18n.translate(
+ 'xpack.observability_onboarding.otelKubernetesPanel.onceYourKubernetesInfrastructureLabel',
+ {
+ defaultMessage:
+ 'Analyse your Kubernetes cluster’s health and monitor your container workloads.',
+ }
+ )}
+
+
+
+ >
+ ) : (
+
+ ),
+ },
+ ]}
+ />
+
+
+ );
+};
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx
index b5b427a718d67d..771948f062fcf6 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/empty_prompt.tsx
@@ -35,7 +35,7 @@ export const EmptyPrompt: FunctionComponent = ({
useEffect(() => {
analytics?.reportEvent(OBSERVABILITY_ONBOARDING_FLOW_ERROR_TELEMETRY_EVENT.eventType, {
onboardingFlowType,
- error,
+ error: error.body?.message ?? error.message,
context: telemetryEventContext,
});
}, [analytics, error, onboardingFlowType, telemetryEventContext]);
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx
index 98fb6c74a37dee..e529a0782e3955 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx
+++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/shared/get_started_panel.tsx
@@ -37,7 +37,7 @@ export function GetStartedPanel({
}: {
onboardingFlowType: string;
dataset: string;
- integration: string;
+ integration?: string;
newTab: boolean;
actionLinks: Array<{
id: string;
@@ -88,7 +88,7 @@ export function GetStartedPanel({
-
+
{actionLinks.map(({ id, title, label, href }) => (
@@ -110,30 +110,33 @@ export function GetStartedPanel({
-
-
-
-
- {i18n.translate(
- 'xpack.observability_onboarding.dataIngestStatus.viewAllAssetsLinkText',
- {
- defaultMessage: 'View all assets',
- }
- )}
-
- ),
- }}
- />
-
+ {integration && (
+ <>
+
+
+
+ {i18n.translate(
+ 'xpack.observability_onboarding.dataIngestStatus.viewAllAssetsLinkText',
+ {
+ defaultMessage: 'View all assets',
+ }
+ )}
+
+ ),
+ }}
+ />
+
+ >
+ )}
>
);
}
diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts
index 7f63ec61249f5e..33a501bd184b91 100644
--- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts
+++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/kubernetes/route.ts
@@ -29,10 +29,14 @@ export interface HasKubernetesDataRouteResponse {
const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerRoute({
endpoint: 'POST /internal/observability_onboarding/kubernetes/flow',
+ params: t.type({
+ body: t.type({ pkgName: t.union([t.literal('kubernetes'), t.literal('kubernetes_otel')]) }),
+ }),
options: { tags: [] },
async handler({
context,
request,
+ params,
plugins,
services,
kibanaVersion,
@@ -55,8 +59,14 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR
const [{ encoded: apiKeyEncoded }, elasticAgentVersion] = await Promise.all([
createShipperApiKey(client.asCurrentUser, 'kubernetes_onboarding'),
getAgentVersion(fleetPluginStart, kibanaVersion),
- packageClient.ensureInstalledPackage({ pkgName: 'kubernetes' }),
+ // System package is always required
packageClient.ensureInstalledPackage({ pkgName: 'system' }),
+ // Kubernetes package is required for both classic kubernetes and otel
+ packageClient.ensureInstalledPackage({ pkgName: 'kubernetes' }),
+ // Kubernetes otel package is required only for otel
+ params.body.pkgName === 'kubernetes_otel'
+ ? packageClient.ensureInstalledPackage({ pkgName: 'kubernetes_otel' })
+ : undefined,
]);
const elasticsearchUrlList = plugins.cloud?.setup?.elasticsearchUrl
From 2d6874981ec036dd992c2481a13228aed5bd68a2 Mon Sep 17 00:00:00 2001
From: Ying Mao
Date: Tue, 8 Oct 2024 14:25:23 -0400
Subject: [PATCH 39/50] Fixing flaky backfill tests (#194328)
Resolves https://github.com/elastic/kibana/issues/192144 and
https://github.com/elastic/kibana/issues/181862
## Summary
Unskipped the test that was skipped in
https://github.com/elastic/kibana/issues/181862 but it did not fail at
all during the 2400 times total I ran the flaky test runner so I'm going
to unskip and see if it comes back ever
While running the flaky test runner, I saw this test from
https://github.com/elastic/kibana/issues/192144 be flaky so I addressed
in this PR. This was caused by the query for the alert documents
sometimes not returning the alert docs in timestamp order, which is
important because I'm trying to compare the actual alert timestamp to
the expected alert timestamp (in order). Added a sort order to the alert
query.
---------
Co-authored-by: Elastic Machine
---
.../group1/tests/alerting/backfill/find.ts | 3 +-
.../tests/alerting/backfill/task_runner.ts | 31 ++++++++++++-------
2 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/find.ts
index 829c6770b67aeb..91f10338ac0071 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/find.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/find.ts
@@ -135,8 +135,7 @@ export default function findBackfillTests({ getService }: FtrProviderContext) {
for (const scenario of UserAtSpaceScenarios) {
const { user, space } = scenario;
- // FLAKY: https://github.com/elastic/kibana/issues/181862
- describe.skip(scenario.id, () => {
+ describe(scenario.id, () => {
const apiOptions = {
spaceId: space.id,
username: user.username,
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/task_runner.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/task_runner.ts
index e66121a4c33db3..e9fa1724f4bca5 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/task_runner.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/backfill/task_runner.ts
@@ -53,6 +53,7 @@ import {
export default function createBackfillTaskRunnerTests({ getService }: FtrProviderContext) {
const es = getService('es');
const retry = getService('retry');
+ const log = getService('log');
const esTestIndexTool = new ESTestIndexTool(es, retry);
const supertestWithoutAuth = getService('supertestWithoutAuth');
const supertest = getService('supertest');
@@ -65,25 +66,25 @@ export default function createBackfillTaskRunnerTests({ getService }: FtrProvide
moment().utc().subtract(14, 'days').toISOString(),
// backfill execution set 1
- moment().utc().startOf('day').subtract(13, 'days').add(64, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(13, 'days').add(65, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(13, 'days').add(66, 'seconds').toISOString(),
+ moment().utc().startOf('day').subtract(13, 'days').add(10, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(13, 'days').add(11, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(13, 'days').add(12, 'minutes').toISOString(),
// backfill execution set 2
- moment().utc().startOf('day').subtract(12, 'days').add(89, 'seconds').toISOString(),
+ moment().utc().startOf('day').subtract(12, 'days').add(20, 'minutes').toISOString(),
// backfill execution set 3
- moment().utc().startOf('day').subtract(11, 'days').add(785, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(11, 'days').add(888, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(11, 'days').add(954, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(11, 'days').add(1045, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(11, 'days').add(1145, 'seconds').toISOString(),
+ moment().utc().startOf('day').subtract(11, 'days').add(30, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(11, 'days').add(31, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(11, 'days').add(32, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(11, 'days').add(33, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(11, 'days').add(34, 'minutes').toISOString(),
// backfill execution set 4 purposely left empty
// after last backfill
- moment().utc().startOf('day').subtract(9, 'days').add(666, 'seconds').toISOString(),
- moment().utc().startOf('day').subtract(9, 'days').add(667, 'seconds').toISOString(),
+ moment().utc().startOf('day').subtract(9, 'days').add(40, 'minutes').toISOString(),
+ moment().utc().startOf('day').subtract(9, 'days').add(41, 'minutes').toISOString(),
];
describe('ad hoc backfill task', () => {
@@ -175,6 +176,9 @@ export default function createBackfillTaskRunnerTests({ getService }: FtrProvide
.send([{ rule_id: ruleId, start, end }])
.expect(200);
+ log.info(`originalDocTimestamps ${JSON.stringify(originalDocTimestamps)}`);
+ log.info(`scheduledBackfill ${JSON.stringify(response2.body)}`);
+
const scheduleResult = response2.body;
expect(scheduleResult.length).to.eql(1);
@@ -668,7 +672,10 @@ export default function createBackfillTaskRunnerTests({ getService }: FtrProvide
async function queryForAlertDocs(): Promise>> {
const searchResult = await es.search({
index: alertsAsDataIndex,
- body: { query: { match_all: {} } },
+ body: {
+ sort: [{ [ALERT_ORIGINAL_TIME]: { order: 'asc' } }],
+ query: { match_all: {} },
+ },
});
return searchResult.hits.hits as Array>;
}
From 407137a6befb38f34cb11f6a3b6a741a27977031 Mon Sep 17 00:00:00 2001
From: Paulo Silva
Date: Tue, 8 Oct 2024 11:56:08 -0700
Subject: [PATCH 40/50] [Fleet] add escapeMultilineString Handlebar helper
(#195159)
## Summary
Adding a handlebar helper to escape multiline strings. It has the same
function as `escapeStringHelper`, but does not wrap strings in single
quotes, allowing concatenation of escaped variables in the `hbs`
template such as this example:
```hbs
audit_rules: "{{escape_multiline_string audit_rules}}
{{escape_multiline_string "
# Session data audit rules
-a always,exit -F arch=b64 -S execve,execveat -k exec
-a always,exit -F arch=b64 -S exit_group
-a always,exit -F arch=b64 -S setsid"}}"
{{else}}
{{#if audit_rules}}
audit_rules: {{escape_string audit_rules}}
{{/if}}
{{/if}}
```
The above would not be possible using only `escape_string` as
`audit_rules` would be wrapped in single quotes.
## Screenshots
The example above illustrates how this option allows the Auditd manager
integration to append Session data audit rules to the `audit_rules`
field when Session data is enabled in the integration
([PR](https://github.com/elastic/integrations/pull/11336)).
---
.../server/services/epm/agent/agent.test.ts | 74 +++++++++++++++++++
.../fleet/server/services/epm/agent/agent.ts | 12 +++
2 files changed, 86 insertions(+)
diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts
index a3e5749384c0bb..7a2a175c4697f1 100644
--- a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts
@@ -261,6 +261,80 @@ New lines and \\n escaped values.`,
});
});
+ describe('escape_multiline_string helper', () => {
+ it('should escape new lines', () => {
+ const streamTemplate = `
+ input: log
+ multiline_text: "{{escape_multiline_string multiline_text}}"
+ `;
+
+ const vars = {
+ multiline_text: {
+ type: 'textarea',
+ value: `This is a text with
+New lines and \n escaped values.`,
+ },
+ };
+
+ const output = compileTemplate(vars, streamTemplate);
+ expect(output).toEqual({
+ input: 'log',
+ multiline_text: `This is a text with
+New lines and
+escaped values.`,
+ });
+ });
+
+ it('should escape single quotes', () => {
+ const streamTemplate = `
+ input: log
+ multiline_text: "{{escape_multiline_string multiline_text}}"
+ `;
+
+ const vars = {
+ multiline_text: {
+ type: 'textarea',
+ value: `This is a multiline text with
+'escaped values.'`,
+ },
+ };
+
+ const output = compileTemplate(vars, streamTemplate);
+ expect(output).toEqual({
+ input: 'log',
+ multiline_text: `This is a multiline text with
+''escaped values.''`,
+ });
+ });
+
+ it('should allow concatenation of multiline strings', () => {
+ const streamTemplate = `
+input: log
+multiline_text: "{{escape_multiline_string multiline_text}}{{escape_multiline_string "
+This is a concatenated text
+with new lines"}}"
+ `;
+
+ const vars = {
+ multiline_text: {
+ type: 'textarea',
+ value: `This is a text with
+New lines and\nescaped values.`,
+ },
+ };
+
+ const output = compileTemplate(vars, streamTemplate);
+ expect(output).toEqual({
+ input: 'log',
+ multiline_text: `This is a text with
+New lines and
+escaped values.
+This is a concatenated text
+with new lines`,
+ });
+ });
+ });
+
describe('to_json helper', () => {
const streamTemplate = `
input: log
diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts
index 29d6c3c18fc569..8cfa5305413a59 100644
--- a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts
+++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts
@@ -131,6 +131,18 @@ function escapeStringHelper(str: string) {
}
handlebars.registerHelper('escape_string', escapeStringHelper);
+/**
+ * escapeMultilineStringHelper will escape a multiline string by doubling the newlines
+ * and escaping single quotes.
+ * This is useful when the string is multiline and needs to be escaped in a yaml file
+ * without wrapping it in single quotes.
+ */
+function escapeMultilineStringHelper(str: string) {
+ if (!str) return undefined;
+ return str.replace(/\'/g, "''").replace(/\n/g, '\n\n');
+}
+handlebars.registerHelper('escape_multiline_string', escapeMultilineStringHelper);
+
// toJsonHelper will convert any object to a Json string.
function toJsonHelper(value: any) {
if (typeof value === 'string') {
From 0c5c7124766af7e719bf57fe54497d0084541938 Mon Sep 17 00:00:00 2001
From: Dzmitry Lemechko
Date: Tue, 8 Oct 2024 21:17:38 +0200
Subject: [PATCH 41/50] [ftr] print response body for API key creation failure
(#195379)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
We are currently investigating test failures against ESS deployment:
Failed to create API key for user `admin` role
```
[00:00:00] └-> "before all" hook in "Burn rate rule"
[00:00:00] │ debg new cloud SAML authentication with 'admin' role
[00:00:00] │ debg Requesting url (redacted): [https://bk-stateful-ftr-16-2c9ee40b2d03.kb.eu-west-1.aws.qa.cld.elstc.co/api/status]
[00:00:00] │ info Reading cloud user credentials from /root/.qaf/data/git/kibana/.ftr/role_users.json
[00:00:04] └- ✖ fail: apis Slo - Burn rate rule Burn rate rule "before all" hook in "Burn rate rule"
[00:00:04] │ Error: expected 400 to equal 200
[00:00:04] │ at Assertion.assert (expect.js:100:11)
[00:00:04] │ at Assertion.apply (expect.js:227:8)
[00:00:04] │ at Assertion.be (expect.js:69:22)
[00:00:04] │ at Object.createM2mApiKeyWithRoleScope (saml_auth_provider.ts:113:25)
[00:00:04] │ at processTicksAndRejections (node:internal/process/task_queues:95:5)
[00:00:04] │ at Context. (burn_rate_rule.ts:40:24)
[00:00:04] │ at Object.apply (wrap_function.js:74:16)
```
Currently we get only status code and it doesn't tell much about the
issue. This PR will print response body + status code
---
.../services/saml_auth/saml_auth_provider.ts | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts
index 67d77583af7134..1ee239ac5448e8 100644
--- a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts
+++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts
@@ -101,7 +101,7 @@ export function SamlAuthProvider({ getService }: FtrProviderContext) {
};
}
- const { body, status } = await supertestWithoutAuth
+ const response = await supertestWithoutAuth
.post('/internal/security/api_key')
.set(INTERNAL_REQUEST_HEADERS)
.set(adminCookieHeader)
@@ -110,9 +110,14 @@ export function SamlAuthProvider({ getService }: FtrProviderContext) {
metadata: {},
role_descriptors: roleDescriptors,
});
- expect(status).to.be(200);
- const apiKey = body;
+ if (response.status !== 200) {
+ throw new Error(
+ `Failed to create API key for '${role}' role with response text: ${response.text}`
+ );
+ }
+
+ const apiKey = response.body;
const apiKeyHeader = { Authorization: 'ApiKey ' + apiKey.encoded };
log.debug(`Created api key for role: [${role}]`);
From 187afe78ce7b047026b3d9dd6e98a67fe92a3047 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 8 Oct 2024 20:21:09 +0100
Subject: [PATCH 42/50] skip flaky suite (#170370)
---
.../response_actions/response_console/process_operations.cy.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts
index e09aa8dc9fc859..9484122c013d7f 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts
@@ -26,7 +26,8 @@ import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_
const AGENT_BEAT_FILE_PATH_SUFFIX = '/components/agentbeat';
-describe('Response console', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
+// FLAKY: https://github.com/elastic/kibana/issues/170370
+describe.skip('Response console', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
beforeEach(() => {
login();
});
From 31f4f2c9842f3f6713e004be22087379e9ccf3e7 Mon Sep 17 00:00:00 2001
From: Marco Vettorello
Date: Tue, 8 Oct 2024 21:23:12 +0200
Subject: [PATCH 43/50] [deps] Replace compare-versions with semver (#195287)
## Summary
We are reducing the number of dependencies by replacing the
`compare-versions` library with the already used `semver` library that
offer the same functionality.
---
package.json | 1 -
packages/kbn-sort-predicates/src/sorting.ts | 7 +++++--
.../vis_types/vega/public/data_model/vega_parser.ts | 8 ++++++--
.../public/pages/rules/rules_container.tsx | 7 +++++--
yarn.lock | 5 -----
5 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/package.json b/package.json
index 1b4a27b012a602..cd72443e901720 100644
--- a/package.json
+++ b/package.json
@@ -1075,7 +1075,6 @@
"classnames": "2.2.6",
"color": "^4.2.3",
"commander": "^4.1.1",
- "compare-versions": "3.5.1",
"constate": "^3.3.2",
"copy-to-clipboard": "^3.0.8",
"core-js": "^3.37.1",
diff --git a/packages/kbn-sort-predicates/src/sorting.ts b/packages/kbn-sort-predicates/src/sorting.ts
index d56ca8f550c801..8adfc237bc7f54 100644
--- a/packages/kbn-sort-predicates/src/sorting.ts
+++ b/packages/kbn-sort-predicates/src/sorting.ts
@@ -7,8 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import versionCompare from 'compare-versions';
import valid from 'semver/functions/valid';
+import semVerCompare from 'semver/functions/compare';
+import semVerCoerce from 'semver/functions/coerce';
import ipaddr, { type IPv4, type IPv6 } from 'ipaddr.js';
import { FieldFormat } from '@kbn/field-formats-plugin/common';
import moment from 'moment';
@@ -154,7 +155,9 @@ const versionComparison: CompareFn = (v1, v2, direction) => {
if (bInvalid) {
return direction * -1;
}
- return versionCompare(valueA, valueB);
+ const semVerValueA = semVerCoerce(valueA) ?? '';
+ const semVerValueB = semVerCoerce(valueB) ?? '';
+ return semVerCompare(semVerValueA, semVerValueB);
};
const openRange = { gte: -Infinity, lt: Infinity };
diff --git a/src/plugins/vis_types/vega/public/data_model/vega_parser.ts b/src/plugins/vis_types/vega/public/data_model/vega_parser.ts
index d41ec9759373ca..0a0d0ec4151aec 100644
--- a/src/plugins/vis_types/vega/public/data_model/vega_parser.ts
+++ b/src/plugins/vis_types/vega/public/data_model/vega_parser.ts
@@ -9,7 +9,8 @@
import _ from 'lodash';
import schemaParser from 'vega-schema-url-parser';
-import versionCompare from 'compare-versions';
+import semVerCompare from 'semver/functions/compare';
+import semVerCoerce from 'semver/functions/coerce';
import hjson from 'hjson';
import { euiPaletteColorBlind } from '@elastic/eui';
import { euiThemeVars } from '@kbn/ui-theme';
@@ -17,6 +18,7 @@ import { i18n } from '@kbn/i18n';
import { logger, Warn, None, version as vegaVersion } from 'vega';
import { compile, TopLevelSpec, version as vegaLiteVersion } from 'vega-lite';
+
import { EsQueryParser } from './es_query_parser';
import { Utils } from './utils';
import { EmsFileParser } from './ems_file_parser';
@@ -558,8 +560,10 @@ The URL is an identifier only. Kibana and your browser will never access this UR
const schema = schemaParser(spec.$schema);
const isVegaLite = schema.library === 'vega-lite';
const libVersion = isVegaLite ? vegaLiteVersion : vegaVersion;
+ const schemaSemVer = semVerCoerce(schema.version) ?? '';
+ const libSemVersion = semVerCoerce(libVersion) ?? '';
- if (versionCompare(schema.version, libVersion) > 0) {
+ if (semVerCompare(schemaSemVer, libSemVersion) > 0) {
this._onWarning(
i18n.translate(
'visTypeVega.vegaParser.notValidLibraryVersionForInputSpecWarningMessage',
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx
index 1b1af9aab42923..8eaec34adf1cfe 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx
@@ -5,7 +5,6 @@
* 2.0.
*/
import React, { useState, useMemo, useEffect } from 'react';
-import compareVersions from 'compare-versions';
import { EuiSpacer } from '@elastic/eui';
import { useParams, useHistory, generatePath } from 'react-router-dom';
import type {
@@ -14,6 +13,8 @@ import type {
RuleStateAttributes,
} from '@kbn/cloud-security-posture-common/schema/rules/latest';
import { extractErrorMessage } from '@kbn/cloud-security-posture-common';
+import semVerCompare from 'semver/functions/compare';
+import semVerCoerce from 'semver/functions/coerce';
import { benchmarksNavigation } from '../../common/navigation/constants';
import { buildRuleKey } from '../../../common/utils/rules_states';
import { RulesTable } from './rules_table';
@@ -197,7 +198,9 @@ export const RulesContainer = () => {
return a.localeCompare(b, 'en', { sensitivity: 'base' });
});
- const cleanedRuleNumberList = [...new Set(ruleNumberList)].sort(compareVersions);
+ const cleanedRuleNumberList = [...new Set(ruleNumberList)].sort((a, b) =>
+ semVerCompare(semVerCoerce(a) ?? '', semVerCoerce(b) ?? '')
+ );
const rulesPageData = useMemo(
() => getRulesPageData(filteredRulesWithStates, status, error, rulesQuery),
diff --git a/yarn.lock b/yarn.lock
index 465601b23df341..1c831df8f53e8b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14706,11 +14706,6 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
-compare-versions@3.5.1:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393"
- integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==
-
compare-versions@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a"
From 4ad15861489904aae1b8a0bb3d35bf3fca6eb729 Mon Sep 17 00:00:00 2001
From: Tre
Date: Tue, 8 Oct 2024 20:23:33 +0100
Subject: [PATCH 44/50] [Ownership] Fixup wrong assignment (#195462)
## Summary
Fixup line broken by this
[merge](https://github.com/elastic/kibana/pull/195272).
Contributes to: https://github.com/elastic/kibana/issues/194815
---
.github/CODEOWNERS | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d4c8f52704e967..a20e12d88c3525 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1013,6 +1013,8 @@ packages/kbn-zod-helpers @elastic/security-detection-rule-management
# The #CC# prefix delineates Code Coverage,
# used for the 'team' designator within Kibana Stats
+x-pack/test_serverless/api_integration/test_suites/common/platform_security @elastic/kibana-security
+
# Data Discovery
/x-pack/test_serverless/functional/es_archives/pre_calculated_histogram @elastic/kibana-data-discovery
/x-pack/test_serverless/functional/es_archives/kibana_sample_data_flights_index_pattern @elastic/kibana-data-discovery
@@ -1511,7 +1513,6 @@ x-pack/test/api_integration/apis/management/index_management/inference_endpoints
/x-pack/test/functional/es_archives/auditbeat/default @elastic/security-solution
/x-pack/test/functional/es_archives/auditbeat/hosts @elastic/security-solution
/x-pack/test_serverless/functional/page_objects/svl_management_page.ts @elastic/security-solution
-/x-pack/test_serverless/api_integration/test_suites/common/platform_security/ @elastic/security-solution
/x-pack/test_serverless/api_integration/test_suites/security @elastic/security-solution
/x-pack/test_serverless/functional/page_objects/svl_sec_landing_page.ts @elastic/security-solution
From e98fa183aaa050999b524425b7dd1479d64c9426 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 8 Oct 2024 20:27:10 +0100
Subject: [PATCH 45/50] skip flaky suite (#193294)
---
.../test_suites/observability/onboarding/firehose.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts b/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts
index 22c700d16be2df..c2d2161c7491a8 100644
--- a/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts
+++ b/x-pack/test_serverless/functional/test_suites/observability/onboarding/firehose.ts
@@ -20,7 +20,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const synthtrace = getService('svlLogsSynthtraceClient');
- describe('Onboarding Firehose Quickstart Flow', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/193294
+ describe.skip('Onboarding Firehose Quickstart Flow', () => {
before(async () => {
await PageObjects.svlCommonPage.loginAsAdmin(); // Onboarding requires admin role
await PageObjects.common.navigateToUrlWithBrowserHistory(
From 13c949462931960a4ca5eb82f9dbe955893d426b Mon Sep 17 00:00:00 2001
From: "elastic-renovate-prod[bot]"
<174716857+elastic-renovate-prod[bot]@users.noreply.github.com>
Date: Tue, 8 Oct 2024 21:44:39 +0200
Subject: [PATCH 46/50] Update dependency re2js to v0.4.2 (main) (#193897)
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index cd72443e901720..02b7f61924abbc 100644
--- a/package.json
+++ b/package.json
@@ -1199,7 +1199,7 @@
"query-string": "^6.13.2",
"rbush": "^3.0.1",
"re-resizable": "^6.9.9",
- "re2js": "0.4.1",
+ "re2js": "0.4.2",
"react": "^17.0.2",
"react-ace": "^7.0.5",
"react-diff-view": "^3.2.1",
diff --git a/yarn.lock b/yarn.lock
index 1c831df8f53e8b..c3ec4698df7d2d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -26595,10 +26595,10 @@ re-resizable@^6.9.9:
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.9.tgz#99e8b31c67a62115dc9c5394b7e55892265be216"
integrity sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==
-re2js@0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/re2js/-/re2js-0.4.1.tgz#74a87a90b79ab5dc1effed818151354c8faccb3d"
- integrity sha512-Kxb+OKXrEPowP4bXAF07NDXtgYX07S8HeVGgadx5/D/R41LzWg1kgTD2szIv2iHJM3vrAPnDKaBzfUE/7QWX9w==
+re2js@0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/re2js/-/re2js-0.4.2.tgz#e344697e64d128ea65c121d6581e67ee5bfa5feb"
+ integrity sha512-wuv0p0BGbrVIkobV8zh82WjDurXko0QNCgaif6DdRAljgVm2iio4PVYCwjAxGaWen1/QZXWDM67dIslmz7AIbA==
react-ace@^7.0.5:
version "7.0.5"
From 942a1f1e693c5ff86cbcbd93d65bde0915fed274 Mon Sep 17 00:00:00 2001
From: Mario Duarte
Date: Tue, 8 Oct 2024 21:57:54 +0200
Subject: [PATCH 47/50] CP-7782 - Replace E2E pipeline execution with
serverless-quality-gates CHECK_SYNTHETICS (#195214)
---
.../emergency/pipeline.tests-production.yaml | 12 +++++++-----
.../quality-gates/emergency/pipeline.tests-qa.yaml | 12 +++++++-----
.../emergency/pipeline.tests-staging.yaml | 12 +++++++-----
.../quality-gates/pipeline.tests-production.yaml | 12 +++++++-----
.../pipelines/quality-gates/pipeline.tests-qa.yaml | 12 +++++++-----
.../quality-gates/pipeline.tests-staging.yaml | 12 +++++++-----
6 files changed, 42 insertions(+), 30 deletions(-)
diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml
index f7689ffeab9285..c81208c7cf61c9 100644
--- a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml
+++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-production.yaml
@@ -16,14 +16,16 @@ steps:
DEPLOYMENT_SLICES: ${DEPLOYMENT_SLICES:-""}
soft_fail: true
- - label: ":rocket: control-plane e2e tests"
+ - label: ":rocket: Run serverless synthetics check"
if: build.env("ENVIRONMENT") == "production-canary"
- trigger: "ess-k8s-production-e2e-tests" # https://buildkite.com/elastic/ess-k8s-production-e2e-tests
+ trigger: "serverless-quality-gates"
build:
- env:
- REGION_ID: aws-us-east-1
- NAME_PREFIX: ci_test_kibana-promotion_
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-production.yaml)"
+ env:
+ TARGET_ENV: production
+ SERVICE: kibana
+ CHECK_SYNTHETICS: true
+ CHECK_SYNTHETICS_TAG: serverless-platform-core-validation
- label: ":cookie: 24h bake time before continuing promotion"
if: build.env("ENVIRONMENT") == "production-canary"
diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml
index 1c0e69ef7a7b4e..3c2f123914c3df 100644
--- a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml
+++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-qa.yaml
@@ -3,10 +3,12 @@
# A failure in this pipeline build will prevent further progression to the subsequent stage.
steps:
- - label: ":rocket: control-plane e2e tests"
- trigger: "ess-k8s-qa-e2e-tests-daily" # https://buildkite.com/elastic/ess-k8s-qa-e2e-tests-daily
+ - label: ":rocket: Run serverless synthetics check"
+ trigger: "serverless-quality-gates"
build:
- env:
- REGION_ID: aws-eu-west-1
- NAME_PREFIX: ci_test_kibana-promotion_
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)"
+ env:
+ TARGET_ENV: qa
+ SERVICE: kibana
+ CHECK_SYNTHETICS: true
+ CHECK_SYNTHETICS_TAG: serverless-platform-core-validation
\ No newline at end of file
diff --git a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml
index 2bd85e2ad8a741..5c2da1b4fe891a 100644
--- a/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml
+++ b/.buildkite/pipelines/quality-gates/emergency/pipeline.tests-staging.yaml
@@ -3,13 +3,15 @@
# A failure in this pipeline build will prevent further progression to the subsequent stage.
steps:
- - label: ":rocket: control-plane e2e tests"
- trigger: "ess-k8s-staging-e2e-tests" # https://buildkite.com/elastic/ess-k8s-staging-e2e-tests
+ - label: ":rocket: Run serverless synthetics check"
+ trigger: "serverless-quality-gates"
build:
- env:
- REGION_ID: aws-us-east-1
- NAME_PREFIX: ci_test_kibana-promotion_
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)"
+ env:
+ TARGET_ENV: staging
+ SERVICE: kibana
+ CHECK_SYNTHETICS: true
+ CHECK_SYNTHETICS_TAG: serverless-platform-core-validation
- label: ":kibana: Kibana Serverless Tests for ${ENVIRONMENT}"
trigger: appex-qa-serverless-kibana-ftr-tests # https://buildkite.com/elastic/appex-qa-serverless-kibana-ftr-tests
diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml
index f7689ffeab9285..c81208c7cf61c9 100644
--- a/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml
+++ b/.buildkite/pipelines/quality-gates/pipeline.tests-production.yaml
@@ -16,14 +16,16 @@ steps:
DEPLOYMENT_SLICES: ${DEPLOYMENT_SLICES:-""}
soft_fail: true
- - label: ":rocket: control-plane e2e tests"
+ - label: ":rocket: Run serverless synthetics check"
if: build.env("ENVIRONMENT") == "production-canary"
- trigger: "ess-k8s-production-e2e-tests" # https://buildkite.com/elastic/ess-k8s-production-e2e-tests
+ trigger: "serverless-quality-gates"
build:
- env:
- REGION_ID: aws-us-east-1
- NAME_PREFIX: ci_test_kibana-promotion_
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-production.yaml)"
+ env:
+ TARGET_ENV: production
+ SERVICE: kibana
+ CHECK_SYNTHETICS: true
+ CHECK_SYNTHETICS_TAG: serverless-platform-core-validation
- label: ":cookie: 24h bake time before continuing promotion"
if: build.env("ENVIRONMENT") == "production-canary"
diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml
index 3d9363279507f2..6730ad5c5840da 100644
--- a/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml
+++ b/.buildkite/pipelines/quality-gates/pipeline.tests-qa.yaml
@@ -29,13 +29,15 @@ steps:
ENVIRONMENT: ${ENVIRONMENT}
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)"
- - label: ":rocket: control-plane e2e tests"
- trigger: "ess-k8s-qa-e2e-tests-daily" # https://buildkite.com/elastic/ess-k8s-qa-e2e-tests-daily
+ - label: ":rocket: Run serverless synthetics check"
+ trigger: "serverless-quality-gates"
build:
- env:
- REGION_ID: aws-eu-west-1
- NAME_PREFIX: ci_test_kibana-promotion_
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-qa.yaml)"
+ env:
+ TARGET_ENV: qa
+ SERVICE: kibana
+ CHECK_SYNTHETICS: true
+ CHECK_SYNTHETICS_TAG: serverless-platform-core-validation
- wait: ~
diff --git a/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml b/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml
index 2bd85e2ad8a741..5c2da1b4fe891a 100644
--- a/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml
+++ b/.buildkite/pipelines/quality-gates/pipeline.tests-staging.yaml
@@ -3,13 +3,15 @@
# A failure in this pipeline build will prevent further progression to the subsequent stage.
steps:
- - label: ":rocket: control-plane e2e tests"
- trigger: "ess-k8s-staging-e2e-tests" # https://buildkite.com/elastic/ess-k8s-staging-e2e-tests
+ - label: ":rocket: Run serverless synthetics check"
+ trigger: "serverless-quality-gates"
build:
- env:
- REGION_ID: aws-us-east-1
- NAME_PREFIX: ci_test_kibana-promotion_
message: "${BUILDKITE_MESSAGE} (triggered by pipeline.tests-staging.yaml)"
+ env:
+ TARGET_ENV: staging
+ SERVICE: kibana
+ CHECK_SYNTHETICS: true
+ CHECK_SYNTHETICS_TAG: serverless-platform-core-validation
- label: ":kibana: Kibana Serverless Tests for ${ENVIRONMENT}"
trigger: appex-qa-serverless-kibana-ftr-tests # https://buildkite.com/elastic/appex-qa-serverless-kibana-ftr-tests
From 91c045d698b2e68afd13f5d4bef9229d8a231abe Mon Sep 17 00:00:00 2001
From: Hannah Mudge
Date: Tue, 8 Oct 2024 14:34:01 -0600
Subject: [PATCH 48/50] [Canvas] Cleanup services (#194634)
Closes https://github.com/elastic/kibana/issues/194050
## Summary
This PR refactors the Canvas services to no longer use the
`PluginServiceProvider` from the `PresentationUtil` plugin. Note that
the Canvas storybooks are broken on main (and they have been for who
knows how long) and so, while I did make some changes to the storybooks
to make them **compile**, I didn't bother to get them fully functional.
Note that the Ecommerce workpad is broken - this is not due to this PR,
it is a [bug](https://github.com/elastic/kibana/issues/195297) that is
present on main.
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---------
Co-authored-by: Catherine Liu
---
.../renderers/embeddable/embeddable.tsx | 5 +-
x-pack/plugins/canvas/jest.config.js | 1 +
.../services/nav_link.ts => jest_setup.ts} | 7 +-
x-pack/plugins/canvas/public/application.tsx | 25 +--
.../canvas/public/components/app/index.tsx | 26 ++-
.../components/asset_manager/asset_manager.ts | 6 +-
.../datasource/datasource_component.js | 11 +-
.../datasource/datasource_preview/index.js | 7 +-
.../components/element_content/index.tsx | 9 +-
.../embeddable_flyout/flyout.component.tsx | 35 ++-
.../components/embeddable_flyout/flyout.tsx | 7 +-
.../es_data_view_select.component.tsx | 8 +-
.../es_data_view_select.tsx | 7 +-
.../components/es_field_select/index.tsx | 7 +-
.../es_fields_select/es_fields_select.tsx | 7 +-
.../public/components/expression/index.tsx | 7 +-
.../public/components/function_form/index.tsx | 10 +-
.../components/function_form_list/index.js | 4 +-
.../public/components/home/home.stories.tsx | 2 +-
.../home/hooks/use_clone_workpad.ts | 7 +-
.../home/hooks/use_create_from_template.ts | 7 +-
.../home/hooks/use_create_workpad.ts | 7 +-
.../home/hooks/use_delete_workpad.ts | 83 ++++----
.../home/hooks/use_find_templates.ts | 6 +-
.../components/home/hooks/use_find_workpad.ts | 8 +-
.../home/hooks/use_import_workpad.ts | 7 +-
.../my_workpads/my_workpads.component.tsx | 2 +-
.../home/my_workpads/my_workpads.stories.tsx | 2 +-
.../home/my_workpads/my_workpads.tsx | 2 +-
.../my_workpads/workpad_table.component.tsx | 2 +-
.../my_workpads/workpad_table.stories.tsx | 4 +-
.../home/my_workpads/workpad_table.tsx | 11 +-
.../workpad_table_tools.component.tsx | 2 +-
.../workpad_templates.stories.tsx | 2 +-
.../public/components/home_app/home_app.tsx | 7 +-
.../hooks/workpad/use_download_workpad.ts | 7 +-
.../hooks/workpad/use_incoming_embeddable.ts | 8 +-
.../saved_elements_modal.tsx | 7 +-
.../public/components/workpad/workpad.tsx | 6 +-
.../hooks/use_canvas_filters.ts | 6 +-
.../editor_menu/editor_menu.tsx | 23 +-
.../labs_control/labs_control.tsx | 10 +-
.../flyout/hooks/use_download_runtime.ts | 18 +-
.../workpad_header/share_menu/share_menu.tsx | 22 +-
.../canvas/public/functions/filters.ts | 6 +-
x-pack/plugins/canvas/public/lib/assets.ts | 5 +-
.../canvas/public/lib/create_handlers.ts | 7 +-
.../canvas/public/lib/data_view_helpers.ts | 26 +++
.../public/lib/element_handler_creators.ts | 11 +-
.../plugins/canvas/public/lib/fullscreen.js | 7 +-
.../canvas/public/lib/template_service.ts | 5 +-
x-pack/plugins/canvas/public/plugin.tsx | 27 ++-
.../use_fullscreen_presentation_helper.ts | 12 +-
.../routes/workpad/hooks/use_workpad.test.tsx | 18 +-
.../routes/workpad/hooks/use_workpad.ts | 22 +-
.../hooks/use_workpad_persist.test.tsx | 15 +-
.../workpad/hooks/use_workpad_persist.ts | 15 +-
.../workpad/workpad_presentation_helper.tsx | 9 +-
.../services/canvas_custom_element_service.ts | 62 ++++++
...sions.ts => canvas_expressions_service.ts} | 61 +++---
.../filters.ts => canvas_filters_service.ts} | 30 ++-
.../notify.ts => canvas_notify_service.ts} | 22 +-
.../public/services/canvas_workpad_service.ts | 200 ++++++++++++++++++
.../canvas/public/services/custom_element.ts | 21 --
.../canvas/public/services/data_views.ts | 14 --
.../canvas/public/services/embeddables.ts | 22 --
.../canvas/public/services/expressions.ts | 8 -
.../plugins/canvas/public/services/filters.ts | 8 -
.../plugins/canvas/public/services/index.ts | 56 +----
.../public/services/kibana/custom_element.ts | 44 ----
.../public/services/kibana/data_views.ts | 50 -----
.../public/services/kibana/embeddables.ts | 22 --
.../canvas/public/services/kibana/index.ts | 66 ------
.../canvas/public/services/kibana/labs.ts | 23 --
.../canvas/public/services/kibana/nav_link.ts | 28 ---
.../canvas/public/services/kibana/platform.ts | 46 ----
.../public/services/kibana/reporting.ts | 45 ----
.../public/services/kibana/ui_actions.ts | 19 --
.../public/services/kibana/visualizations.ts | 21 --
.../canvas/public/services/kibana/workpad.ts | 170 ---------------
.../canvas/public/services/kibana_services.ts | 76 +++++++
x-pack/plugins/canvas/public/services/labs.ts | 14 --
.../public/services/legacy/reporting.ts | 42 ----
.../plugins/canvas/public/services/mocks.ts | 125 +++++++++++
.../plugins/canvas/public/services/notify.ts | 15 --
.../canvas/public/services/platform.ts | 37 ----
.../canvas/public/services/reporting.ts | 13 --
.../canvas/public/services/storybook/index.ts | 60 ------
.../public/services/storybook/notify.ts | 22 --
.../public/services/storybook/workpad.ts | 122 -----------
.../public/services/stubs/custom_element.ts | 21 --
.../public/services/stubs/data_views.ts | 26 ---
.../public/services/stubs/embeddables.ts | 20 --
.../public/services/stubs/expressions.ts | 41 ----
.../canvas/public/services/stubs/filters.ts | 23 --
.../canvas/public/services/stubs/index.ts | 61 ------
.../canvas/public/services/stubs/labs.ts | 25 ---
.../canvas/public/services/stubs/nav_link.ts | 17 --
.../canvas/public/services/stubs/notify.ts | 21 --
.../canvas/public/services/stubs/platform.ts | 39 ----
.../public/services/stubs/reporting.tsx | 17 --
.../public/services/stubs/ui_actions.ts | 17 --
.../public/services/stubs/visualizations.ts | 19 --
.../canvas/public/services/stubs/workpad.ts | 115 ----------
.../canvas/public/services/ui_actions.ts | 12 --
.../canvas/public/services/visualizations.ts | 14 --
.../plugins/canvas/public/services/workpad.ts | 43 ----
.../canvas/public/state/actions/elements.js | 32 ++-
.../canvas/public/state/actions/filters.js | 13 ++
.../canvas/public/state/initial_state.js | 7 +-
.../canvas/public/state/reducers/elements.js | 16 +-
.../public/state/reducers/embeddable.ts | 7 +-
.../public/state/reducers/resolved_args.js | 8 +-
.../canvas/public/state/reducers/transient.js | 31 ++-
.../canvas/public/state/reducers/workpad.js | 52 ++---
.../__snapshots__/app.test.tsx.snap | 2 +-
.../shareable_runtime/components/app.test.tsx | 14 +-
.../__snapshots__/settings.test.tsx.snap | 20 +-
.../footer/settings/settings.test.tsx | 14 +-
.../shareable_runtime/test/selectors.ts | 2 +-
x-pack/plugins/canvas/storybook/constants.ts | 31 +++
.../decorators/services_decorator.tsx | 14 +-
x-pack/plugins/canvas/tsconfig.json | 7 +-
123 files changed, 989 insertions(+), 2018 deletions(-)
rename x-pack/plugins/canvas/{public/services/nav_link.ts => jest_setup.ts} (65%)
create mode 100644 x-pack/plugins/canvas/public/lib/data_view_helpers.ts
create mode 100644 x-pack/plugins/canvas/public/services/canvas_custom_element_service.ts
rename x-pack/plugins/canvas/public/services/{kibana/expressions.ts => canvas_expressions_service.ts} (65%)
rename x-pack/plugins/canvas/public/services/{kibana/filters.ts => canvas_filters_service.ts} (51%)
rename x-pack/plugins/canvas/public/services/{kibana/notify.ts => canvas_notify_service.ts} (71%)
create mode 100644 x-pack/plugins/canvas/public/services/canvas_workpad_service.ts
delete mode 100644 x-pack/plugins/canvas/public/services/custom_element.ts
delete mode 100644 x-pack/plugins/canvas/public/services/data_views.ts
delete mode 100644 x-pack/plugins/canvas/public/services/embeddables.ts
delete mode 100644 x-pack/plugins/canvas/public/services/expressions.ts
delete mode 100644 x-pack/plugins/canvas/public/services/filters.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/custom_element.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/data_views.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/embeddables.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/index.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/labs.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/nav_link.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/platform.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/reporting.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/ui_actions.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/visualizations.ts
delete mode 100644 x-pack/plugins/canvas/public/services/kibana/workpad.ts
create mode 100644 x-pack/plugins/canvas/public/services/kibana_services.ts
delete mode 100644 x-pack/plugins/canvas/public/services/labs.ts
delete mode 100644 x-pack/plugins/canvas/public/services/legacy/reporting.ts
create mode 100644 x-pack/plugins/canvas/public/services/mocks.ts
delete mode 100644 x-pack/plugins/canvas/public/services/notify.ts
delete mode 100644 x-pack/plugins/canvas/public/services/platform.ts
delete mode 100644 x-pack/plugins/canvas/public/services/reporting.ts
delete mode 100644 x-pack/plugins/canvas/public/services/storybook/index.ts
delete mode 100644 x-pack/plugins/canvas/public/services/storybook/notify.ts
delete mode 100644 x-pack/plugins/canvas/public/services/storybook/workpad.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/custom_element.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/data_views.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/embeddables.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/expressions.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/filters.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/index.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/labs.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/nav_link.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/notify.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/platform.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/reporting.tsx
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/ui_actions.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/visualizations.ts
delete mode 100644 x-pack/plugins/canvas/public/services/stubs/workpad.ts
delete mode 100644 x-pack/plugins/canvas/public/services/ui_actions.ts
delete mode 100644 x-pack/plugins/canvas/public/services/visualizations.ts
delete mode 100644 x-pack/plugins/canvas/public/services/workpad.ts
create mode 100644 x-pack/plugins/canvas/public/state/actions/filters.js
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
index d088988c243fe4..a21528fb970fb1 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx
@@ -19,7 +19,6 @@ import React, { FC } from 'react';
import ReactDOM from 'react-dom';
import { useSearchApi } from '@kbn/presentation-publishing';
import { omit } from 'lodash';
-import { pluginServices } from '../../../public/services';
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';
import { RendererStrings } from '../../../i18n';
import {
@@ -32,6 +31,7 @@ import { EmbeddableExpression } from '../../expression_types/embeddable';
import { StartDeps } from '../../plugin';
import { embeddableInputToExpression } from './embeddable_input_to_expression';
import { useGetAppContext } from './use_get_app_context';
+import { embeddableService } from '../../../public/services/kibana_services';
const { embeddable: strings } = RendererStrings;
@@ -132,13 +132,12 @@ export const embeddableRendererFactory = (
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (domNode, { input, embeddableType, canvasApi }, handlers) => {
- const { embeddables } = pluginServices.getServices();
const uniqueId = handlers.getElementId();
const isByValueEnabled = plugins.presentationUtil.labsService.isProjectEnabled(
'labs:canvas:byValueEmbeddable'
);
- if (embeddables.reactEmbeddableRegistryHasKey(embeddableType)) {
+ if (embeddableService.reactEmbeddableRegistryHasKey(embeddableType)) {
/**
* Prioritize React embeddables
*/
diff --git a/x-pack/plugins/canvas/jest.config.js b/x-pack/plugins/canvas/jest.config.js
index 2bff284e94ad8b..f7a9224795b4a2 100644
--- a/x-pack/plugins/canvas/jest.config.js
+++ b/x-pack/plugins/canvas/jest.config.js
@@ -17,4 +17,5 @@ module.exports = {
collectCoverageFrom: [
'/x-pack/plugins/canvas/{canvas_plugin_src,common,i18n,public,server,shareable_runtime}/**/*.{js,ts,tsx}',
],
+ setupFiles: ['/x-pack/plugins/canvas/jest_setup.ts'],
};
diff --git a/x-pack/plugins/canvas/public/services/nav_link.ts b/x-pack/plugins/canvas/jest_setup.ts
similarity index 65%
rename from x-pack/plugins/canvas/public/services/nav_link.ts
rename to x-pack/plugins/canvas/jest_setup.ts
index 02c7ca50219a63..d9c0c26ea5a382 100644
--- a/x-pack/plugins/canvas/public/services/nav_link.ts
+++ b/x-pack/plugins/canvas/jest_setup.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
-export interface CanvasNavLinkService {
- updatePath: (path: string) => void;
-}
+import { setStubKibanaServices } from './public/services/mocks';
+
+// Start the kibana services with stubs
+setStubKibanaServices();
diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx
index 72440e3e6873c3..222b64e4175e92 100644
--- a/x-pack/plugins/canvas/public/application.tsx
+++ b/x-pack/plugins/canvas/public/application.tsx
@@ -19,7 +19,6 @@ import { AppMountParameters, CoreStart, CoreSetup, AppUpdater } from '@kbn/core/
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
-import { PluginServices } from '@kbn/presentation-util-plugin/public';
import { CanvasStartDeps, CanvasSetupDeps } from './plugin';
import { App } from './components/app';
@@ -32,12 +31,7 @@ import { init as initStatsReporter } from './lib/ui_metric';
import { CapabilitiesStrings } from '../i18n';
-import {
- startLegacyServices,
- services,
- LegacyServicesProvider,
- CanvasPluginServices,
-} from './services';
+import { startLegacyServices, services, LegacyServicesProvider } from './services';
import { initFunctions } from './functions';
// @ts-expect-error untyped local
import { appUnload } from './state/actions/app';
@@ -56,29 +50,26 @@ export const renderApp = ({
startPlugins,
params,
canvasStore,
- pluginServices,
+ appUpdater,
}: {
coreStart: CoreStart;
startPlugins: CanvasStartDeps;
params: AppMountParameters;
canvasStore: Store;
- pluginServices: PluginServices;
+ appUpdater: BehaviorSubject;
}) => {
const { element } = params;
element.classList.add('canvas');
element.classList.add('canvasContainerWrapper');
- const ServicesContextProvider = pluginServices.getContextProvider();
ReactDOM.render(
-
-
-
-
-
-
-
+
+
+
+
+
,
element
diff --git a/x-pack/plugins/canvas/public/components/app/index.tsx b/x-pack/plugins/canvas/public/components/app/index.tsx
index e23891ccc9bca6..0ae3fcf95e6bee 100644
--- a/x-pack/plugins/canvas/public/components/app/index.tsx
+++ b/x-pack/plugins/canvas/public/components/app/index.tsx
@@ -5,14 +5,17 @@
* 2.0.
*/
-import React, { FC, useEffect } from 'react';
+import { AppUpdater, ScopedHistory } from '@kbn/core/public';
import PropTypes from 'prop-types';
-import { ScopedHistory } from '@kbn/core/public';
-import { useNavLinkService } from '../../services';
+import React, { FC, useEffect } from 'react';
+import { BehaviorSubject } from 'rxjs';
// @ts-expect-error
import { shortcutManager } from '../../lib/shortcut_manager';
import { CanvasRouter } from '../../routes';
import { Flyouts } from '../flyouts';
+import { getSessionStorage } from '../../lib/storage';
+import { SESSIONSTORAGE_LASTPATH } from '../../../common/lib';
+import { coreServices } from '../../services/kibana_services';
class ShortcutManagerContextWrapper extends React.Component> {
static childContextTypes = {
@@ -28,12 +31,21 @@ class ShortcutManagerContextWrapper extends React.Component = ({ history }) => {
- const { updatePath } = useNavLinkService();
-
+export const App: FC<{ history: ScopedHistory; appUpdater: BehaviorSubject }> = ({
+ history,
+ appUpdater,
+}) => {
useEffect(() => {
return history.listen(({ pathname, search }) => {
- updatePath(pathname + search);
+ const path = pathname + search;
+ appUpdater.next(() => ({
+ defaultPath: path,
+ }));
+
+ getSessionStorage().set(
+ `${SESSIONSTORAGE_LASTPATH}:${coreServices.http.basePath.get()}`,
+ path
+ );
});
});
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts
index b46c9f07f7caa9..582c5174b663fb 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts
+++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.ts
@@ -20,7 +20,7 @@ import { State, AssetType, CanvasWorkpad } from '../../../types';
import { AssetManager as Component } from './asset_manager.component';
import { getFullWorkpadPersisted } from '../../state/selectors/workpad';
-import { pluginServices } from '../../services';
+import { getCanvasWorkpadService } from '../../services/canvas_workpad_service';
export const AssetManager = connect(
(state: State) => ({
@@ -31,7 +31,7 @@ export const AssetManager = connect(
onAddAsset: (workpad: CanvasWorkpad, type: AssetType['type'], content: AssetType['value']) => {
// make the ID here and pass it into the action
const asset = createAsset(type, content);
- const { notify, workpad: workpadService } = pluginServices.getServices();
+ const workpadService = getCanvasWorkpadService();
return workpadService
.updateAssets(workpad.id, { ...workpad.assets, [asset.id]: asset })
@@ -40,7 +40,7 @@ export const AssetManager = connect(
// then return the id, so the caller knows the id that will be created
return asset.id;
})
- .catch((error) => notifyError(error, notify.error));
+ .catch((error) => notifyError(error));
},
}),
(stateProps, dispatchProps, ownProps) => {
diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js
index 4b64149d2a8f64..0d610155362941 100644
--- a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js
+++ b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js
@@ -20,7 +20,7 @@ import {
import { isEqual } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { pluginServices } from '../../services';
+import { dataViewsService } from '../../services/kibana_services';
import { DatasourceSelector } from './datasource_selector';
import { DatasourcePreview } from './datasource_preview';
@@ -67,12 +67,9 @@ export class DatasourceComponent extends PureComponent {
state = { defaultIndex: '' };
componentDidMount() {
- pluginServices
- .getServices()
- .dataViews.getDefaultDataView()
- .then((defaultDataView) => {
- this.setState({ defaultIndex: defaultDataView.title });
- });
+ dataViewsService.getDefaultDataView().then((defaultDataView) => {
+ this.setState({ defaultIndex: defaultDataView.title });
+ });
}
componentDidUpdate(prevProps) {
diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js
index 1ca674bfb6f9d3..f6f535058ea21d 100644
--- a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js
+++ b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js
@@ -8,18 +8,17 @@
import React, { useState, useEffect } from 'react';
import { PropTypes } from 'prop-types';
import { Loading } from '../../loading';
-import { useExpressionsService } from '../../../services';
+import { getCanvasExpressionService } from '../../../services/canvas_expressions_service';
import { DatasourcePreview as Component } from './datasource_preview';
export const DatasourcePreview = (props) => {
const [datatable, setDatatable] = useState();
- const expressionsService = useExpressionsService();
useEffect(() => {
- expressionsService
+ getCanvasExpressionService()
.interpretAst({ type: 'expression', chain: [props.function] }, {})
.then(setDatatable);
- }, [expressionsService, props.function, setDatatable]);
+ }, [props.function, setDatatable]);
if (!datatable) {
return ;
diff --git a/x-pack/plugins/canvas/public/components/element_content/index.tsx b/x-pack/plugins/canvas/public/components/element_content/index.tsx
index bdbfc205d4c810..72ff04cbf20557 100644
--- a/x-pack/plugins/canvas/public/components/element_content/index.tsx
+++ b/x-pack/plugins/canvas/public/components/element_content/index.tsx
@@ -5,23 +5,24 @@
* 2.0.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { getSelectedPage, getPageById } from '../../state/selectors/workpad';
-import { useExpressionsService } from '../../services';
import { ElementContent as Component, Props as ComponentProps } from './element_content';
import { State } from '../../../types';
+import { getCanvasExpressionService } from '../../services/canvas_expressions_service';
export type Props = Omit;
export const ElementContent = (props: Props) => {
- const expressionsService = useExpressionsService();
const selectedPageId = useSelector(getSelectedPage);
const backgroundColor =
useSelector((state: State) => getPageById(state, selectedPageId)?.style.background) || '';
const { renderable } = props;
- const renderFunction = renderable ? expressionsService.getRenderer(renderable.as) : null;
+ const renderFunction = useMemo(() => {
+ return renderable ? getCanvasExpressionService().getRenderer(renderable.as) : null;
+ }, [renderable]);
return ;
};
diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx
index 9adde7ab57718d..fbb7b971bfcb47 100644
--- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx
+++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.component.tsx
@@ -5,14 +5,18 @@
* 2.0.
*/
-import React, { FC, useCallback, useMemo } from 'react';
-import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody, EuiTitle } from '@elastic/eui';
+import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import React, { FC, useCallback, useMemo } from 'react';
-import { SavedObjectFinder, SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public';
-import { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
import { EmbeddableFactory, ReactEmbeddableSavedObject } from '@kbn/embeddable-plugin/public';
-import { useEmbeddablesService, usePlatformService } from '../../services';
+import { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
+import { SavedObjectFinder, SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public';
+import {
+ contentManagementService,
+ coreServices,
+ embeddableService,
+} from '../../services/kibana_services';
const strings = {
getNoItemsText: () =>
@@ -45,13 +49,8 @@ export const AddEmbeddableFlyout: FC = ({
onClose,
isByValueEnabled,
}) => {
- const embeddablesService = useEmbeddablesService();
- const platformService = usePlatformService();
- const { getEmbeddableFactories, getReactEmbeddableSavedObjects } = embeddablesService;
- const { getContentManagement, getUISettings } = platformService;
-
const legacyFactoriesBySavedObjectType: LegacyFactoryMap = useMemo(() => {
- return [...getEmbeddableFactories()]
+ return [...embeddableService.getEmbeddableFactories()]
.filter(
(embeddableFactory) =>
Boolean(embeddableFactory.savedObjectMetaData?.type) && !embeddableFactory.isContainerType
@@ -60,10 +59,10 @@ export const AddEmbeddableFlyout: FC = ({
acc[factory.savedObjectMetaData!.type] = factory;
return acc;
}, {} as LegacyFactoryMap);
- }, [getEmbeddableFactories]);
+ }, []);
const factoriesBySavedObjectType: FactoryMap = useMemo(() => {
- return [...getReactEmbeddableSavedObjects()]
+ return [...embeddableService.getReactEmbeddableSavedObjects()]
.filter(([type, embeddableFactory]) => {
return Boolean(embeddableFactory.savedObjectMetaData?.type);
})
@@ -74,7 +73,7 @@ export const AddEmbeddableFlyout: FC = ({
};
return acc;
}, {} as FactoryMap);
- }, [getReactEmbeddableSavedObjects]);
+ }, []);
const metaData = useMemo(
() =>
@@ -111,7 +110,7 @@ export const AddEmbeddableFlyout: FC = ({
onSelect(id, type, isByValueEnabled);
return;
}
- const embeddableFactories = getEmbeddableFactories();
+ const embeddableFactories = embeddableService.getEmbeddableFactories();
// Find the embeddable type from the saved object type
const found = Array.from(embeddableFactories).find((embeddableFactory) => {
return Boolean(
@@ -124,7 +123,7 @@ export const AddEmbeddableFlyout: FC = ({
onSelect(id, foundEmbeddableType, isByValueEnabled);
},
- [isByValueEnabled, getEmbeddableFactories, onSelect, factoriesBySavedObjectType]
+ [isByValueEnabled, onSelect, factoriesBySavedObjectType]
);
return (
@@ -141,8 +140,8 @@ export const AddEmbeddableFlyout: FC = ({
showFilter={true}
noItemsMessage={strings.getNoItemsText()}
services={{
- contentClient: getContentManagement().client,
- uiSettings: getUISettings(),
+ contentClient: contentManagementService.client,
+ uiSettings: coreServices.uiSettings,
}}
/>
diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx
index d2772a04cdc2ec..3ecabeb8974f1d 100644
--- a/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx
+++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/flyout.tsx
@@ -15,7 +15,7 @@ import { getSelectedPage } from '../../state/selectors/workpad';
import { EmbeddableTypes } from '../../../canvas_plugin_src/expression_types/embeddable';
import { embeddableInputToExpression } from '../../../canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression';
import { State } from '../../../types';
-import { useLabsService } from '../../services';
+import { presentationUtilService } from '../../services/kibana_services';
const allowedEmbeddables = {
[EmbeddableTypes.map]: (id: string) => {
@@ -67,8 +67,9 @@ export const AddEmbeddablePanel: React.FunctionComponent = ({
availableEmbeddables,
...restProps
}) => {
- const labsService = useLabsService();
- const isByValueEnabled = labsService.isProjectEnabled('labs:canvas:byValueEmbeddable');
+ const isByValueEnabled = presentationUtilService.labsService.isProjectEnabled(
+ 'labs:canvas:byValueEmbeddable'
+ );
const dispatch = useDispatch();
const pageId = useSelector((state) => getSelectedPage(state));
diff --git a/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx
index 4539b1f7274fcd..18a98630635cb3 100644
--- a/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx
+++ b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx
@@ -7,14 +7,12 @@
import React, { FocusEventHandler } from 'react';
import { EuiComboBox } from '@elastic/eui';
-import { DataView } from '@kbn/data-views-plugin/common';
-
-type DataViewOption = Pick;
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
export interface ESDataViewSelectProps {
loading: boolean;
value: string;
- dataViews: DataViewOption[];
+ dataViews: DataViewListItem[];
onChange: (string: string) => void;
onBlur: FocusEventHandler | undefined;
onFocus: FocusEventHandler | undefined;
@@ -31,7 +29,7 @@ export const ESDataViewSelect: React.FunctionComponent =
onFocus,
onBlur,
}) => {
- const selectedDataView = dataViews.find((view) => value === view.title) as DataViewOption;
+ const selectedDataView = dataViews.find((view) => value === view.title);
const selectedOption = selectedDataView
? { value: selectedDataView.title, label: selectedDataView.name || selectedDataView.title }
diff --git a/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx
index 271662755d32a8..217b7c79c8e754 100644
--- a/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx
+++ b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx
@@ -5,25 +5,24 @@
* 2.0.
*/
-import { DataView } from '@kbn/data-views-plugin/common';
+import { DataViewListItem } from '@kbn/data-views-plugin/common';
import { sortBy } from 'lodash';
import React, { FC, useRef, useState } from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
-import { useDataViewsService } from '../../services';
import {
ESDataViewSelect as Component,
ESDataViewSelectProps as Props,
} from './es_data_view_select.component';
+import { getDataViews } from '../../lib/data_view_helpers';
type ESDataViewSelectProps = Omit;
export const ESDataViewSelect: FC = (props) => {
const { value, onChange } = props;
- const [dataViews, setDataViews] = useState>>([]);
+ const [dataViews, setDataViews] = useState([]);
const [loading, setLoading] = useState(true);
const mounted = useRef(true);
- const { getDataViews } = useDataViewsService();
useEffectOnce(() => {
getDataViews().then((newDataViews) => {
diff --git a/x-pack/plugins/canvas/public/components/es_field_select/index.tsx b/x-pack/plugins/canvas/public/components/es_field_select/index.tsx
index 653eec22d77d9f..f41f67bb6ee953 100644
--- a/x-pack/plugins/canvas/public/components/es_field_select/index.tsx
+++ b/x-pack/plugins/canvas/public/components/es_field_select/index.tsx
@@ -6,8 +6,8 @@
*/
import React, { useState, useEffect, useRef } from 'react';
-import { useDataViewsService } from '../../services';
import { ESFieldSelect as Component, ESFieldSelectProps as Props } from './es_field_select';
+import { getDataViewFields } from '../../lib/data_view_helpers';
type ESFieldSelectProps = Omit;
@@ -15,17 +15,16 @@ export const ESFieldSelect: React.FunctionComponent = (props
const { index, value, onChange } = props;
const [fields, setFields] = useState([]);
const loadingFields = useRef(false);
- const { getFields } = useDataViewsService();
useEffect(() => {
loadingFields.current = true;
- getFields(index)
+ getDataViewFields(index)
.then((newFields) => setFields(newFields || []))
.finally(() => {
loadingFields.current = false;
});
- }, [index, getFields]);
+ }, [index]);
useEffect(() => {
if (!loadingFields.current && value && !fields.includes(value)) {
diff --git a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx
index c929203f9e094c..0cde66199b4b35 100644
--- a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx
+++ b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx
@@ -8,11 +8,11 @@
import React, { useState, useEffect, useRef } from 'react';
import { isEqual } from 'lodash';
import usePrevious from 'react-use/lib/usePrevious';
-import { useDataViewsService } from '../../services';
import {
ESFieldsSelect as Component,
ESFieldsSelectProps as Props,
} from './es_fields_select.component';
+import { getDataViewFields } from '../../lib/data_view_helpers';
type ESFieldsSelectProps = Omit & { index: string };
@@ -21,11 +21,10 @@ export const ESFieldsSelect: React.FunctionComponent = (pro
const [fields, setFields] = useState([]);
const prevIndex = usePrevious(index);
const mounted = useRef(true);
- const { getFields } = useDataViewsService();
useEffect(() => {
if (prevIndex !== index) {
- getFields(index).then((newFields) => {
+ getDataViewFields(index).then((newFields) => {
if (!mounted.current) {
return;
}
@@ -37,7 +36,7 @@ export const ESFieldsSelect: React.FunctionComponent = (pro
}
});
}
- }, [fields, index, onChange, prevIndex, selected, getFields]);
+ }, [fields, index, onChange, prevIndex, selected]);
useEffect(
() => () => {
diff --git a/x-pack/plugins/canvas/public/components/expression/index.tsx b/x-pack/plugins/canvas/public/components/expression/index.tsx
index 37bbbd1f5a4c1e..1d0c9915bf3471 100644
--- a/x-pack/plugins/canvas/public/components/expression/index.tsx
+++ b/x-pack/plugins/canvas/public/components/expression/index.tsx
@@ -8,7 +8,6 @@
import React, { FC, useState, useCallback, useMemo, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fromExpression } from '@kbn/interpreter';
-import { useExpressionsService } from '../../services';
import { getSelectedPage, getSelectedElement } from '../../state/selectors/workpad';
// @ts-expect-error
import { setExpression, flushContext } from '../../state/actions/elements';
@@ -16,6 +15,7 @@ import { setExpression, flushContext } from '../../state/actions/elements';
import { ElementNotSelected } from './element_not_selected';
import { Expression as Component } from './expression';
import { State, CanvasElement } from '../../../types';
+import { getCanvasExpressionService } from '../../services/canvas_expressions_service';
interface ExpressionProps {
done: () => void;
@@ -45,7 +45,6 @@ export const Expression: FC = ({ done }) => {
};
const ExpressionContainer: FC = ({ done, element, pageId }) => {
- const expressions = useExpressionsService();
const dispatch = useDispatch();
const [isCompact, setCompact] = useState(true);
const toggleCompactView = useCallback(() => {
@@ -111,8 +110,8 @@ const ExpressionContainer: FC = ({ done, element, page
}, [element, setFormState, formState]);
const functionDefinitions = useMemo(
- () => Object.values(expressions.getFunctions()),
- [expressions]
+ () => Object.values(getCanvasExpressionService().getFunctions()),
+ []
);
return (
diff --git a/x-pack/plugins/canvas/public/components/function_form/index.tsx b/x-pack/plugins/canvas/public/components/function_form/index.tsx
index 202bb4384d6961..f58eb496f7139c 100644
--- a/x-pack/plugins/canvas/public/components/function_form/index.tsx
+++ b/x-pack/plugins/canvas/public/components/function_form/index.tsx
@@ -34,8 +34,8 @@ import { findExistingAsset } from '../../lib/find_existing_asset';
import { FunctionForm as Component } from './function_form';
import { Args, ArgType, ArgTypeDef } from '../../expression_types/types';
import { State, ExpressionContext, CanvasElement, AssetType } from '../../../types';
-import { useNotifyService, useWorkpadService } from '../../services';
import { createAsset, notifyError } from '../../lib/assets';
+import { getCanvasWorkpadService } from '../../services/canvas_workpad_service';
interface FunctionFormProps {
name: string;
@@ -54,8 +54,6 @@ interface FunctionFormProps {
export const FunctionForm: React.FunctionComponent = (props) => {
const { expressionIndex, ...restProps } = props;
const { nextArgType, path, parentPath, argType } = restProps;
- const service = useWorkpadService();
- const notifyService = useNotifyService();
const dispatch = useDispatch();
const context = useSelector(
@@ -113,16 +111,16 @@ export const FunctionForm: React.FunctionComponent = (props)
// make the ID here and pass it into the action
const asset = createAsset(type, content);
- return service
+ return getCanvasWorkpadService()
.updateAssets(workpad.id, { ...workpad.assets, [asset.id]: asset })
.then((res) => {
dispatch(setAsset(asset));
// then return the id, so the caller knows the id that will be created
return asset.id;
})
- .catch((error) => notifyError(error, notifyService.error));
+ .catch((error) => notifyError(error));
},
- [dispatch, notifyService, service, workpad.assets, workpad.id]
+ [dispatch, workpad.assets, workpad.id]
);
const onAssetAdd = useCallback(
diff --git a/x-pack/plugins/canvas/public/components/function_form_list/index.js b/x-pack/plugins/canvas/public/components/function_form_list/index.js
index 0ad6651e3f57e9..6de7b81b12b01e 100644
--- a/x-pack/plugins/canvas/public/components/function_form_list/index.js
+++ b/x-pack/plugins/canvas/public/components/function_form_list/index.js
@@ -8,7 +8,7 @@
import { compose, withProps } from 'react-recompose';
import { get } from 'lodash';
import { toExpression } from '@kbn/interpreter';
-import { pluginServices } from '../../services';
+import { getCanvasExpressionService } from '../../services/canvas_expressions_service';
import { getArgTypeDef } from '../../lib/args';
import { FunctionFormList as Component } from './function_form_list';
@@ -78,7 +78,7 @@ const componentFactory = ({
parentPath,
removable,
}) => {
- const { expressions } = pluginServices.getServices();
+ const expressions = getCanvasExpressionService();
return {
args,
nestedFunctionsArgs: argsWithExprFunctions,
diff --git a/x-pack/plugins/canvas/public/components/home/home.stories.tsx b/x-pack/plugins/canvas/public/components/home/home.stories.tsx
index 0130f9f3f894ba..cfebd509c80ef0 100644
--- a/x-pack/plugins/canvas/public/components/home/home.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/home/home.stories.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { reduxDecorator } from '../../../storybook';
-import { argTypes } from '../../services/storybook';
+import { argTypes } from '../../../storybook/constants';
import { Home } from './home';
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts
index 001a711a58a726..90a7c4c33de899 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_clone_workpad.ts
@@ -9,16 +9,17 @@ import { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { useNotifyService } from '../../../services';
import { getId } from '../../../lib/get_id';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useCloneWorkpad = () => {
- const workpadService = useWorkpadService();
const notifyService = useNotifyService();
const history = useHistory();
return useCallback(
async (workpadId: string) => {
+ const workpadService = getCanvasWorkpadService();
try {
let workpad = await workpadService.get(workpadId);
@@ -35,7 +36,7 @@ export const useCloneWorkpad = () => {
notifyService.error(err, { title: errors.getCloneFailureErrorMessage() });
}
},
- [notifyService, workpadService, history]
+ [notifyService, history]
);
};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts
index 968f9398ba8577..6b57cf61ca905e 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_create_from_template.ts
@@ -9,15 +9,16 @@ import { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { CanvasTemplate } from '../../../../types';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { useNotifyService } from '../../../services';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useCreateFromTemplate = () => {
- const workpadService = useWorkpadService();
const notifyService = useNotifyService();
const history = useHistory();
return useCallback(
async (template: CanvasTemplate) => {
+ const workpadService = getCanvasWorkpadService();
try {
const result = await workpadService.createFromTemplate(template.id);
history.push(`/workpad/${result.id}/page/1`);
@@ -27,6 +28,6 @@ export const useCreateFromTemplate = () => {
});
}
},
- [workpadService, notifyService, history]
+ [notifyService, history]
);
};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts
index 3290bc8227a292..f950a4adcd037a 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_create_workpad.ts
@@ -11,17 +11,18 @@ import { i18n } from '@kbn/i18n';
// @ts-expect-error
import { getDefaultWorkpad } from '../../../state/defaults';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { useNotifyService } from '../../../services';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
import type { CanvasWorkpad } from '../../../../types';
export const useCreateWorkpad = () => {
- const workpadService = useWorkpadService();
const notifyService = useNotifyService();
const history = useHistory();
return useCallback(
async (_workpad?: CanvasWorkpad | null) => {
+ const workpadService = getCanvasWorkpadService();
const workpad = _workpad || (getDefaultWorkpad() as CanvasWorkpad);
try {
@@ -34,7 +35,7 @@ export const useCreateWorkpad = () => {
}
return;
},
- [notifyService, history, workpadService]
+ [notifyService, history]
);
};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts
index 722ddae7411c92..dc52fe2e82d5cb 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_delete_workpad.ts
@@ -8,51 +8,48 @@
import { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { getCanvasNotifyService } from '../../../services/canvas_notify_service';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useDeleteWorkpads = () => {
- const workpadService = useWorkpadService();
- const notifyService = useNotifyService();
-
- return useCallback(
- async (workpadIds: string[]) => {
- const removedWorkpads = workpadIds.map(async (id) => {
- try {
- await workpadService.remove(id);
- return { id, err: null };
- } catch (err) {
- return { id, err };
- }
- });
-
- return Promise.all(removedWorkpads).then((results) => {
- const [passes, errored] = results.reduce<[string[], string[]]>(
- ([passesArr, errorsArr], result) => {
- if (result.err) {
- errorsArr.push(result.id);
- } else {
- passesArr.push(result.id);
- }
-
- return [passesArr, errorsArr];
- },
- [[], []]
- );
-
- const removedIds = workpadIds.filter((id) => passes.includes(id));
-
- if (errored.length > 0) {
- notifyService.error(errors.getDeleteFailureErrorMessage());
- }
-
- return {
- removedIds,
- errored,
- };
- });
- },
- [workpadService, notifyService]
- );
+ return useCallback(async (workpadIds: string[]) => {
+ const workpadService = getCanvasWorkpadService();
+
+ const removedWorkpads = workpadIds.map(async (id) => {
+ try {
+ await workpadService.remove(id);
+ return { id, err: null };
+ } catch (err) {
+ return { id, err };
+ }
+ });
+
+ return Promise.all(removedWorkpads).then((results) => {
+ const [passes, errored] = results.reduce<[string[], string[]]>(
+ ([passesArr, errorsArr], result) => {
+ if (result.err) {
+ errorsArr.push(result.id);
+ } else {
+ passesArr.push(result.id);
+ }
+
+ return [passesArr, errorsArr];
+ },
+ [[], []]
+ );
+
+ const removedIds = workpadIds.filter((id) => passes.includes(id));
+
+ if (errored.length > 0) {
+ getCanvasNotifyService().error(errors.getDeleteFailureErrorMessage());
+ }
+
+ return {
+ removedIds,
+ errored,
+ };
+ });
+ }, []);
};
const errors = {
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
index 9364a79987908d..426b612774762f 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
@@ -6,10 +6,8 @@
*/
import { useCallback } from 'react';
-
-import { useWorkpadService } from '../../../services';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useFindTemplates = () => {
- const workpadService = useWorkpadService();
- return useCallback(async () => await workpadService.findTemplates(), [workpadService]);
+ return useCallback(async () => await getCanvasWorkpadService().findTemplates(), []);
};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
index 10352d0472e8c3..59ff5cd0561b5b 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
@@ -8,21 +8,21 @@
import { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { useNotifyService } from '../../../services';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useFindWorkpads = () => {
- const workpadService = useWorkpadService();
const notifyService = useNotifyService();
return useCallback(
async (text = '') => {
try {
- return await workpadService.find(text);
+ return await getCanvasWorkpadService().find(text);
} catch (err) {
notifyService.error(err, { title: errors.getFindFailureErrorMessage() });
}
},
- [notifyService, workpadService]
+ [notifyService]
);
};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_import_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_import_workpad.ts
index bd780ee01507d4..bb3f447cd107b7 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_import_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_import_workpad.ts
@@ -9,17 +9,18 @@ import { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { useNotifyService } from '../../../services';
import type { CanvasWorkpad } from '../../../../types';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useImportWorkpad = () => {
- const workpadService = useWorkpadService();
const notifyService = useNotifyService();
const history = useHistory();
return useCallback(
async (workpad: CanvasWorkpad) => {
+ const workpadService = getCanvasWorkpadService();
try {
const importedWorkpad = await workpadService.import(workpad);
history.push(`/workpad/${importedWorkpad.id}/page/1`);
@@ -30,7 +31,7 @@ export const useImportWorkpad = () => {
}
return;
},
- [notifyService, history, workpadService]
+ [notifyService, history]
);
};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx
index d9e3f0e4e2c999..6f25bbf8720cfa 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.component.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { FoundWorkpad } from '../../../services/workpad';
+import { FoundWorkpad } from '../../../services/canvas_workpad_service';
import { UploadDropzone } from './upload_dropzone';
import { HomeEmptyPrompt } from './empty_prompt';
import { WorkpadTable } from './workpad_table';
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx
index 52afd552bbc490..967419e9b8b06f 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.stories.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { EuiPanel } from '@elastic/eui';
import { reduxDecorator } from '../../../../storybook';
-import { argTypes } from '../../../services/storybook';
+import { argTypes } from '../../../../storybook/constants';
import { MyWorkpads as Component } from './my_workpads';
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
index b75c8864ad495c..44b3c5938af6cb 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
@@ -7,7 +7,7 @@
import React, { useState, useEffect, createContext, Dispatch, SetStateAction } from 'react';
import { useFindWorkpads } from '../hooks';
-import { FoundWorkpad } from '../../../services/workpad';
+import { FoundWorkpad } from '../../../services/canvas_workpad_service';
import { Loading } from '../loading';
import { MyWorkpads as Component } from './my_workpads.component';
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx
index c64ab50ad8a096..f8e349351ba4c2 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.component.tsx
@@ -21,7 +21,7 @@ import {
import moment from 'moment';
import { RoutingLink } from '../../routing';
-import { FoundWorkpad } from '../../../services/workpad';
+import { FoundWorkpad } from '../../../services/canvas_workpad_service';
import { WorkpadTableTools } from './workpad_table_tools';
import { WorkpadImport } from './workpad_import';
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx
index 6675dea238cc44..f9418f35e62566 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.stories.tsx
@@ -9,9 +9,9 @@ import React, { useState, useEffect } from 'react';
import { EuiPanel } from '@elastic/eui';
import { reduxDecorator } from '../../../../storybook';
+import { argTypes } from '../../../../storybook/constants';
-import { argTypes } from '../../../services/storybook';
-import { getSomeWorkpads } from '../../../services/stubs/workpad';
+import { getSomeWorkpads } from '../../../services/mocks';
import { WorkpadTable as Component } from './workpad_table';
import { WorkpadsContext } from './my_workpads';
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx
index 6d88691f2eabe5..7bbb3a57ab9ced 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table.tsx
@@ -8,17 +8,16 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
-import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
import type { State } from '../../../../types';
-import { usePlatformService } from '../../../services';
-import { useCloneWorkpad } from '../hooks';
+import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
import { useDownloadWorkpad } from '../../hooks';
+import { useCloneWorkpad } from '../hooks';
-import { WorkpadTable as Component } from './workpad_table.component';
+import { coreServices } from '../../../services/kibana_services';
import { WorkpadsContext } from './my_workpads';
+import { WorkpadTable as Component } from './workpad_table.component';
export const WorkpadTable = () => {
- const platformService = usePlatformService();
const onCloneWorkpad = useCloneWorkpad();
const onExportWorkpad = useDownloadWorkpad();
const context = useContext(WorkpadsContext);
@@ -33,7 +32,7 @@ export const WorkpadTable = () => {
const { workpads } = context;
- const dateFormat = platformService.getUISetting('dateFormat');
+ const dateFormat = coreServices.uiSettings.get('dateFormat');
return ;
};
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx
index 251a40ff4d45d1..148e1973352bc2 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/workpad_table_tools.component.tsx
@@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
import { EuiButton, EuiToolTip, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { ConfirmModal } from '../../confirm_modal';
-import { FoundWorkpad } from '../../../services/workpad';
+import { FoundWorkpad } from '../../../services/canvas_workpad_service';
export interface Props {
workpads: FoundWorkpad[];
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx
index 92583ca845aa80..c6a3347f6c39ae 100644
--- a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx
+++ b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.stories.tsx
@@ -9,7 +9,7 @@ import { EuiPanel } from '@elastic/eui';
import React from 'react';
import { reduxDecorator } from '../../../../storybook';
-import { argTypes } from '../../../services/storybook';
+import { argTypes } from '../../../../storybook/constants';
import { WorkpadTemplates as Component } from './workpad_templates';
diff --git a/x-pack/plugins/canvas/public/components/home_app/home_app.tsx b/x-pack/plugins/canvas/public/components/home_app/home_app.tsx
index 086a737d525e9f..54d9659f5a1d8b 100644
--- a/x-pack/plugins/canvas/public/components/home_app/home_app.tsx
+++ b/x-pack/plugins/canvas/public/components/home_app/home_app.tsx
@@ -11,17 +11,16 @@ import { useDispatch } from 'react-redux';
import { getBaseBreadcrumb } from '../../lib/breadcrumbs';
import { resetWorkpad } from '../../state/actions/workpad';
import { HomeApp as Component } from './home_app.component';
-import { usePlatformService } from '../../services';
+import { coreServices } from '../../services/kibana_services';
export const HomeApp = () => {
- const { setBreadcrumbs } = usePlatformService();
const dispatch = useDispatch();
const onLoad = () => dispatch(resetWorkpad());
const history = useHistory();
useEffect(() => {
- setBreadcrumbs([getBaseBreadcrumb(history)]);
- }, [setBreadcrumbs, history]);
+ coreServices.chrome.setBreadcrumbs([getBaseBreadcrumb(history)]);
+ }, [history]);
return ;
};
diff --git a/x-pack/plugins/canvas/public/components/hooks/workpad/use_download_workpad.ts b/x-pack/plugins/canvas/public/components/hooks/workpad/use_download_workpad.ts
index dadf03a8fac5a8..394274b1eb133d 100644
--- a/x-pack/plugins/canvas/public/components/hooks/workpad/use_download_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/hooks/workpad/use_download_workpad.ts
@@ -8,9 +8,10 @@
import { useCallback } from 'react';
import fileSaver from 'file-saver';
import { i18n } from '@kbn/i18n';
-import { useNotifyService, useWorkpadService } from '../../../services';
+import { useNotifyService } from '../../../services';
import { CanvasWorkpad } from '../../../../types';
import type { CanvasRenderedWorkpad } from '../../../../shareable_runtime/types';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
const strings = {
getDownloadFailureErrorMessage: () =>
@@ -28,12 +29,12 @@ const strings = {
export const useDownloadWorkpad = () => {
const notifyService = useNotifyService();
- const workpadService = useWorkpadService();
const download = useDownloadWorkpadBlob();
return useCallback(
async (workpadId: string) => {
try {
+ const workpadService = getCanvasWorkpadService();
const workpad = await workpadService.get(workpadId);
download(workpad, `canvas-workpad-${workpad.name}-${workpad.id}`);
@@ -41,7 +42,7 @@ export const useDownloadWorkpad = () => {
notifyService.error(err, { title: strings.getDownloadFailureErrorMessage() });
}
},
- [workpadService, notifyService, download]
+ [notifyService, download]
);
};
diff --git a/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts b/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts
index 0a4e66917814dc..50c3e527bbbae6 100644
--- a/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts
+++ b/x-pack/plugins/canvas/public/components/hooks/workpad/use_incoming_embeddable.ts
@@ -12,7 +12,7 @@ import { ErrorStrings } from '../../../../i18n';
import { CANVAS_APP } from '../../../../common/lib';
import { decode } from '../../../../common/lib/embeddable_dataurl';
import { CanvasElement, CanvasPage } from '../../../../types';
-import { useEmbeddablesService, useLabsService, useNotifyService } from '../../../services';
+import { useNotifyService } from '../../../services';
// @ts-expect-error unconverted file
import { addElement, fetchAllRenderables } from '../../../state/actions/elements';
// @ts-expect-error unconverted file
@@ -24,16 +24,16 @@ import {
} from '../../../state/actions/embeddable';
import { clearValue } from '../../../state/actions/resolved_args';
import { embeddableInputToExpression } from '../../../../canvas_plugin_src/renderers/embeddable/embeddable_input_to_expression';
+import { embeddableService, presentationUtilService } from '../../../services/kibana_services';
const { actionsElements: strings } = ErrorStrings;
export const useIncomingEmbeddable = (selectedPage: CanvasPage) => {
- const embeddablesService = useEmbeddablesService();
- const labsService = useLabsService();
+ const labsService = presentationUtilService.labsService;
const dispatch = useDispatch();
const notifyService = useNotifyService();
const isByValueEnabled = labsService.isProjectEnabled('labs:canvas:byValueEmbeddable');
- const stateTransferService = embeddablesService.getStateTransfer();
+ const stateTransferService = embeddableService.getStateTransfer();
// fetch incoming embeddable from state transfer service.
const incomingEmbeddable = stateTransferService.getIncomingEmbeddablePackage(CANVAS_APP, true);
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx
index 19e786edfd5fb0..5aeb8847ab8873 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx
@@ -5,11 +5,11 @@
* 2.0.
*/
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { camelCase } from 'lodash';
import { cloneSubgraphs } from '../../lib/clone_subgraphs';
-import { useNotifyService, useCustomElementService } from '../../services';
+import { useNotifyService } from '../../services';
// @ts-expect-error untyped local
import { selectToplevelNodes } from '../../state/actions/transient';
// @ts-expect-error untyped local
@@ -21,6 +21,7 @@ import {
Props as ComponentProps,
} from './saved_elements_modal.component';
import { PositionedElement, CustomElement } from '../../../types';
+import { getCustomElementService } from '../../services/canvas_custom_element_service';
const customElementAdded = 'elements-custom-added';
@@ -28,7 +29,7 @@ export type Props = Pick;
export const SavedElementsModal = ({ onClose }: Props) => {
const notifyService = useNotifyService();
- const customElementService = useCustomElementService();
+ const customElementService = useMemo(() => getCustomElementService(), []);
const dispatch = useDispatch();
const pageId = useSelector(getSelectedPage);
const [customElements, setCustomElements] = useState([]);
diff --git a/x-pack/plugins/canvas/public/components/workpad/workpad.tsx b/x-pack/plugins/canvas/public/components/workpad/workpad.tsx
index 1e604a6deb8508..6052e6951a8e91 100644
--- a/x-pack/plugins/canvas/public/components/workpad/workpad.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad/workpad.tsx
@@ -24,10 +24,10 @@ import { useZoomHandlers } from '../../lib/app_handler_creators';
import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric';
import { LAUNCHED_FULLSCREEN, LAUNCHED_FULLSCREEN_AUTOPLAY } from '../../../common/lib/constants';
import { WorkpadRoutingContext } from '../../routes/workpad';
-import { usePlatformService } from '../../services';
import { Workpad as WorkpadComponent, Props } from './workpad.component';
import { State } from '../../../types';
import { useIncomingEmbeddable } from '../hooks';
+import { coreServices } from '../../services/kibana_services';
type ContainerProps = Pick;
@@ -40,9 +40,7 @@ export const Workpad: FC = (props) => {
const { isFullscreen, setFullscreen, undo, redo, autoplayInterval, nextPage, previousPage } =
useContext(WorkpadRoutingContext);
- const platformService = usePlatformService();
-
- const hasHeaderBanner = useObservable(platformService.hasHeaderBanner$());
+ const hasHeaderBanner = useObservable(coreServices.chrome.hasHeaderBanner$());
const propsFromState = useSelector((state: State) => {
const { width, height, id: workpadId, css: workpadCss } = getWorkpad(state);
diff --git a/x-pack/plugins/canvas/public/components/workpad_filters/hooks/use_canvas_filters.ts b/x-pack/plugins/canvas/public/components/workpad_filters/hooks/use_canvas_filters.ts
index bdccc8040c5def..9aa9aecdfd5169 100644
--- a/x-pack/plugins/canvas/public/components/workpad_filters/hooks/use_canvas_filters.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_filters/hooks/use_canvas_filters.ts
@@ -5,17 +5,19 @@
* 2.0.
*/
+import { useMemo } from 'react';
import { AstFunction, fromExpression } from '@kbn/interpreter';
import { shallowEqual, useSelector } from 'react-redux';
+
import { State } from '../../../../types';
import { getFiltersByFilterExpressions } from '../../../lib/filter';
import { adaptCanvasFilter } from '../../../lib/filter_adapters';
-import { useFiltersService } from '../../../services';
+import { getCanvasFiltersService } from '../../../services/canvas_filters_service';
const extractExpressionAST = (filters: string[]) => fromExpression(filters.join(' | '));
export function useCanvasFilters(filterExprsToGroupBy: AstFunction[] = []) {
- const filtersService = useFiltersService();
+ const filtersService = useMemo(() => getCanvasFiltersService(), []);
const filterExpressions = useSelector(
(state: State) => filtersService.getFilters(state),
shallowEqual
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/editor_menu/editor_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/editor_menu/editor_menu.tsx
index fd644903ac25da..06d20e919dcbe8 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/editor_menu/editor_menu.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/editor_menu/editor_menu.tsx
@@ -21,11 +21,6 @@ import {
import { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public/actions';
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric';
-import {
- useEmbeddablesService,
- useUiActionsService,
- useVisualizationsService,
-} from '../../../services';
import { CANVAS_APP } from '../../../../common/lib';
import { ElementSpec } from '../../../../types';
import { EditorMenu as Component } from './editor_menu.component';
@@ -33,6 +28,11 @@ import { embeddableInputToExpression } from '../../../../canvas_plugin_src/rende
import { EmbeddableInput as CanvasEmbeddableInput } from '../../../../canvas_plugin_src/expression_types';
import { useCanvasApi } from '../../hooks/use_canvas_api';
import { ADD_CANVAS_ELEMENT_TRIGGER } from '../../../state/triggers/add_canvas_element_trigger';
+import {
+ embeddableService,
+ uiActionsService,
+ visualizationsService,
+} from '../../../services/kibana_services';
interface Props {
/**
@@ -47,18 +47,15 @@ interface UnwrappedEmbeddableFactory {
}
export const EditorMenu: FC = ({ addElement }) => {
- const embeddablesService = useEmbeddablesService();
const { pathname, search, hash } = useLocation();
- const stateTransferService = embeddablesService.getStateTransfer();
- const visualizationsService = useVisualizationsService();
- const uiActions = useUiActionsService();
+ const stateTransferService = embeddableService.getStateTransfer();
const canvasApi = useCanvasApi();
const [addPanelActions, setAddPanelActions] = useState>>([]);
const embeddableFactories = useMemo(
- () => (embeddablesService ? Array.from(embeddablesService.getEmbeddableFactories()) : []),
- [embeddablesService]
+ () => (embeddableService ? Array.from(embeddableService.getEmbeddableFactories()) : []),
+ []
);
const [unwrappedEmbeddableFactories, setUnwrappedEmbeddableFactories] = useState<
@@ -79,7 +76,7 @@ export const EditorMenu: FC = ({ addElement }) => {
useEffect(() => {
let mounted = true;
async function loadPanelActions() {
- const registeredActions = await uiActions?.getTriggerCompatibleActions?.(
+ const registeredActions = await uiActionsService.getTriggerCompatibleActions(
ADD_CANVAS_ELEMENT_TRIGGER,
{ embeddable: canvasApi }
);
@@ -89,7 +86,7 @@ export const EditorMenu: FC = ({ addElement }) => {
return () => {
mounted = false;
};
- }, [uiActions, canvasApi]);
+ }, [canvasApi]);
const createNewVisType = useCallback(
(visType?: BaseVisType | VisTypeAlias) => () => {
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx b/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx
index 75d6f8064f32a6..6e6ca58f0dd72f 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/labs_control/labs_control.tsx
@@ -5,13 +5,14 @@
* 2.0.
*/
-import React, { useState } from 'react';
import { EuiButtonEmpty, EuiNotificationBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import React, { useState } from 'react';
import { LazyLabsFlyout, withSuspense } from '@kbn/presentation-util-plugin/public';
-import { useLabsService } from '../../../services';
+import { UI_SETTINGS } from '../../../../common';
+import { coreServices, presentationUtilService } from '../../../services/kibana_services';
const strings = {
getLabsButtonLabel: () =>
@@ -23,14 +24,13 @@ const strings = {
const Flyout = withSuspense(LazyLabsFlyout, null);
export const LabsControl = () => {
- const { isLabsEnabled, getProjects } = useLabsService();
const [isShown, setIsShown] = useState(false);
- if (!isLabsEnabled()) {
+ if (!coreServices.uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI)) {
return null;
}
- const projects = getProjects(['canvas']);
+ const projects = presentationUtilService.labsService.getProjects(['canvas']);
const overrideCount = Object.values(projects).filter(
(project) => project.status.isOverride
).length;
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/hooks/use_download_runtime.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/hooks/use_download_runtime.ts
index cd1635d65a573a..49bd93c290ce88 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/hooks/use_download_runtime.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/hooks/use_download_runtime.ts
@@ -5,14 +5,16 @@
* 2.0.
*/
-import { useCallback } from 'react';
-import fileSaver from 'file-saver';
import { i18n } from '@kbn/i18n';
+import fileSaver from 'file-saver';
+import { useCallback } from 'react';
import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../../../../../common/lib/constants';
import { ZIP } from '../../../../../../i18n/constants';
-import { usePlatformService, useNotifyService, useWorkpadService } from '../../../../../services';
import type { CanvasRenderedWorkpad } from '../../../../../../shareable_runtime/types';
+import { useNotifyService } from '../../../../../services';
+import { coreServices } from '../../../../../services/kibana_services';
+import { getCanvasWorkpadService } from '../../../../../services/canvas_workpad_service';
const strings = {
getDownloadRuntimeFailureErrorMessage: () =>
@@ -35,28 +37,28 @@ const strings = {
};
export const useDownloadRuntime = () => {
- const platformService = usePlatformService();
const notifyService = useNotifyService();
const downloadRuntime = useCallback(() => {
try {
- const path = `${platformService.getBasePath()}${API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD}`;
+ const path = `${coreServices.http.basePath.get()}${API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD}`;
window.open(path);
return;
} catch (err) {
notifyService.error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() });
}
- }, [platformService, notifyService]);
+ }, [notifyService]);
return downloadRuntime;
};
export const useDownloadZippedRuntime = () => {
- const workpadService = useWorkpadService();
const notifyService = useNotifyService();
const downloadZippedRuntime = useCallback(
(workpad: CanvasRenderedWorkpad) => {
+ const workpadService = getCanvasWorkpadService();
+
const downloadZip = async () => {
try {
let runtimeZipBlob: Blob | undefined;
@@ -80,7 +82,7 @@ export const useDownloadZippedRuntime = () => {
downloadZip();
},
- [notifyService, workpadService]
+ [notifyService]
);
return downloadZippedRuntime;
};
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx
index c66336a9153c03..e4239864c1915c 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx
@@ -9,11 +9,11 @@ import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { State } from '../../../../types';
-import { useReportingService, usePlatformService } from '../../../services';
import { getPages, getWorkpad } from '../../../state/selectors/workpad';
import { useDownloadWorkpad } from '../../hooks';
import { ShareMenu as ShareMenuComponent } from './share_menu.component';
import { getPdfJobParams } from './utils';
+import { kibanaVersion, reportingService } from '../../../services/kibana_services';
const strings = {
getUnknownExportErrorMessage: (type: string) =>
@@ -27,32 +27,30 @@ const strings = {
export const ShareMenu = () => {
const downloadWorkpad = useDownloadWorkpad();
- const reportingService = useReportingService();
- const platformService = usePlatformService();
const { workpad, pageCount } = useSelector((state: State) => ({
workpad: getWorkpad(state),
pageCount: getPages(state).length,
}));
- const ReportingPanelPDFComponent = reportingService.getReportingPanelPDFComponent();
-
const sharingData = {
workpad,
pageCount,
};
- const ReportingComponent =
- ReportingPanelPDFComponent !== null
- ? ({ onClose }: { onClose: () => void }) => (
- getPdfJobParams(sharingData, platformService.getKibanaVersion())}
+ const ReportingComponent = reportingService
+ ? ({ onClose }: { onClose: () => void }) => {
+ const ReportingPanelPDFV2 = reportingService!.components.ReportingPanelPDFV2;
+ return (
+ getPdfJobParams(sharingData, kibanaVersion)}
layoutOption="canvas"
onClose={onClose}
objectId={workpad.id}
/>
- )
- : null;
+ );
+ }
+ : null;
const onExport = useCallback(
(type: string) => {
diff --git a/x-pack/plugins/canvas/public/functions/filters.ts b/x-pack/plugins/canvas/public/functions/filters.ts
index a168020b6eef87..a37953657e1572 100644
--- a/x-pack/plugins/canvas/public/functions/filters.ts
+++ b/x-pack/plugins/canvas/public/functions/filters.ts
@@ -7,10 +7,11 @@
import { fromExpression } from '@kbn/interpreter';
import { get } from 'lodash';
-import { pluginServices } from '../services';
import type { FiltersFunction } from '../../common/functions';
import { buildFiltersFunction } from '../../common/functions';
import { InitializeArguments } from '.';
+import { getCanvasFiltersService } from '../services/canvas_filters_service';
+import { getCanvasExpressionService } from '../services/canvas_expressions_service';
export interface Arguments {
group: string[];
@@ -40,7 +41,8 @@ function getFiltersByGroup(allFilters: string[], groups?: string[], ungrouped =
export function filtersFunctionFactory(initialize: InitializeArguments): () => FiltersFunction {
const fn: FiltersFunction['fn'] = (input, { group, ungrouped }) => {
- const { expressions, filters: filtersService } = pluginServices.getServices();
+ const expressions = getCanvasExpressionService();
+ const filtersService = getCanvasFiltersService();
const filterList = getFiltersByGroup(filtersService.getFilters(), group, ungrouped);
diff --git a/x-pack/plugins/canvas/public/lib/assets.ts b/x-pack/plugins/canvas/public/lib/assets.ts
index d7fd0ecbac57fd..b51cda9216fbce 100644
--- a/x-pack/plugins/canvas/public/lib/assets.ts
+++ b/x-pack/plugins/canvas/public/lib/assets.ts
@@ -6,8 +6,8 @@
*/
import { i18n } from '@kbn/i18n';
import { AssetType, CanvasAsset } from '../../types';
-import { CanvasNotifyService } from '../services/notify';
import { getId } from './get_id';
+import { getCanvasNotifyService } from '../services/canvas_notify_service';
const strings = {
getSaveFailureTitle: () =>
@@ -32,7 +32,8 @@ export const createAsset = (type: AssetType['type'], content: AssetType['value']
'@created': new Date().toISOString(),
});
-export const notifyError = (err: any, notifyErrorFn: CanvasNotifyService['error']) => {
+export const notifyError = (err: any) => {
+ const { error: notifyErrorFn } = getCanvasNotifyService();
const statusCode = err.response && err.response.status;
switch (statusCode) {
case 400:
diff --git a/x-pack/plugins/canvas/public/lib/create_handlers.ts b/x-pack/plugins/canvas/public/lib/create_handlers.ts
index 374bdaff997213..8b78914d770718 100644
--- a/x-pack/plugins/canvas/public/lib/create_handlers.ts
+++ b/x-pack/plugins/canvas/public/lib/create_handlers.ts
@@ -12,8 +12,10 @@ import {
} from '@kbn/expressions-plugin/public';
import { updateEmbeddableExpression, fetchEmbeddableRenderable } from '../state/actions/embeddable';
import { RendererHandlers, CanvasElement } from '../../types';
-import { pluginServices } from '../services';
+import { getCanvasFiltersService } from '../services/canvas_filters_service';
import { clearValue } from '../state/actions/resolved_args';
+// @ts-expect-error unconverted file
+import { fetchAllRenderables } from '../state/actions/elements';
// This class creates stub handlers to ensure every element and renderer fulfills the contract.
// TODO: consider warning if these methods are invoked but not implemented by the renderer...?
@@ -80,7 +82,7 @@ export const createDispatchedHandlerFactory = (
oldElement = element;
}
- const { filters } = pluginServices.getServices();
+ const filters = getCanvasFiltersService();
const handlers: RendererHandlers & {
event: IInterpreterRenderHandlers['event'];
@@ -94,6 +96,7 @@ export const createDispatchedHandlerFactory = (
break;
case 'applyFilterAction':
filters.updateFilter(element.id, event.data);
+ dispatch(fetchAllRenderables());
break;
case 'onComplete':
this.onComplete(event.data);
diff --git a/x-pack/plugins/canvas/public/lib/data_view_helpers.ts b/x-pack/plugins/canvas/public/lib/data_view_helpers.ts
new file mode 100644
index 00000000000000..0bec688f6788ae
--- /dev/null
+++ b/x-pack/plugins/canvas/public/lib/data_view_helpers.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { dataViewsService } from '../services/kibana_services';
+import { getCanvasNotifyService } from '../services/canvas_notify_service';
+import { ErrorStrings } from '../../i18n';
+
+export const getDataViews = async () => {
+ try {
+ return await dataViewsService.getIdsWithTitle();
+ } catch (e) {
+ const { esService: strings } = ErrorStrings;
+ getCanvasNotifyService().error(e, { title: strings.getIndicesFetchErrorMessage() });
+ }
+ return [];
+};
+
+export const getDataViewFields = async (dataViewTitle: string) => {
+ const dataView = await dataViewsService.create({ title: dataViewTitle });
+
+ return dataView.fields.filter((field) => !field.name.startsWith('_')).map((field) => field.name);
+};
diff --git a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts
index 82b98275290408..72a7df3a267754 100644
--- a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts
+++ b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts
@@ -8,10 +8,11 @@
import { camelCase } from 'lodash';
import { getClipboardData, setClipboardData } from './clipboard';
import { cloneSubgraphs } from './clone_subgraphs';
-import { pluginServices } from '../services';
import { getId } from './get_id';
import { PositionedElement } from '../../types';
import { ELEMENT_NUDGE_OFFSET, ELEMENT_SHIFT_OFFSET } from '../../common/lib/constants';
+import { getCanvasNotifyService } from '../services/canvas_notify_service';
+import { getCustomElementService } from '../services/canvas_custom_element_service';
const extractId = (node: { id: string }): string => node.id;
@@ -71,8 +72,8 @@ export const basicHandlerCreators = {
createCustomElement:
({ selectedNodes }: Props) =>
(name = '', description = '', image = ''): void => {
- const notifyService = pluginServices.getServices().notify;
- const customElementService = pluginServices.getServices().customElement;
+ const notifyService = getCanvasNotifyService();
+ const customElementService = getCustomElementService();
if (selectedNodes.length) {
const content = JSON.stringify({ selectedNodes });
@@ -145,7 +146,7 @@ export const clipboardHandlerCreators = {
cutNodes:
({ pageId, removeNodes, selectedNodes }: Props) =>
(): void => {
- const notifyService = pluginServices.getServices().notify;
+ const notifyService = getCanvasNotifyService();
if (selectedNodes.length) {
setClipboardData({ selectedNodes });
@@ -156,7 +157,7 @@ export const clipboardHandlerCreators = {
copyNodes:
({ selectedNodes }: Props) =>
(): void => {
- const notifyService = pluginServices.getServices().notify;
+ const notifyService = getCanvasNotifyService();
if (selectedNodes.length) {
setClipboardData({ selectedNodes });
diff --git a/x-pack/plugins/canvas/public/lib/fullscreen.js b/x-pack/plugins/canvas/public/lib/fullscreen.js
index fd4e0b65785b98..50a9578b07b782 100644
--- a/x-pack/plugins/canvas/public/lib/fullscreen.js
+++ b/x-pack/plugins/canvas/public/lib/fullscreen.js
@@ -5,22 +5,21 @@
* 2.0.
*/
-import { pluginServices } from '../services';
+import { coreServices } from '../services/kibana_services';
export const fullscreenClass = 'canvas-isFullscreen';
export function setFullscreen(fullscreen, doc = document) {
- const platformService = pluginServices.getServices().platform;
const enabled = Boolean(fullscreen);
const body = doc.querySelector('body');
const bodyClassList = body.classList;
const isFullscreen = bodyClassList.contains(fullscreenClass);
if (enabled && !isFullscreen) {
- platformService.setFullscreen(false);
+ coreServices.chrome.setIsVisible(false);
bodyClassList.add(fullscreenClass);
} else if (!enabled && isFullscreen) {
bodyClassList.remove(fullscreenClass);
- platformService.setFullscreen(true);
+ coreServices.chrome.setIsVisible(true);
}
}
diff --git a/x-pack/plugins/canvas/public/lib/template_service.ts b/x-pack/plugins/canvas/public/lib/template_service.ts
index d5ec467f18740e..481733b781d853 100644
--- a/x-pack/plugins/canvas/public/lib/template_service.ts
+++ b/x-pack/plugins/canvas/public/lib/template_service.ts
@@ -9,12 +9,11 @@
import { API_ROUTE_TEMPLATES } from '../../common/lib/constants';
import { fetch } from '../../common/lib/fetch';
-import { pluginServices } from '../services';
import { CanvasTemplate } from '../../types';
+import { coreServices } from '../services/kibana_services';
const getApiPath = function () {
- const platformService = pluginServices.getServices().platform;
- const basePath = platformService.getBasePath();
+ const basePath = coreServices.http.basePath.get();
return `${basePath}${API_ROUTE_TEMPLATES}`;
};
diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx
index 5f385ce5f079b0..bd4e920a56f7e6 100644
--- a/x-pack/plugins/canvas/public/plugin.tsx
+++ b/x-pack/plugins/canvas/public/plugin.tsx
@@ -38,6 +38,7 @@ import { initLoadingIndicator } from './lib/loading_indicator';
import { getPluginApi, CanvasApi } from './plugin_api';
import { setupExpressions } from './setup_expressions';
import { addCanvasElementTrigger } from './state/triggers/add_canvas_element_trigger';
+import { setKibanaServices, untilPluginStartServicesReady } from './services/kibana_services';
export type { CoreStart, CoreSetup };
@@ -121,22 +122,13 @@ export class CanvasPlugin
setupExpressions({ coreSetup, setupPlugins });
// Get start services
- const [coreStart, startPlugins] = await coreSetup.getStartServices();
+ const [[coreStart, startPlugins]] = await Promise.all([
+ coreSetup.getStartServices(),
+ untilPluginStartServicesReady(),
+ ]);
srcPlugin.start(coreStart, startPlugins);
- const { pluginServices } = await import('./services');
- const { pluginServiceRegistry } = await import('./services/kibana');
-
- pluginServices.setRegistry(
- pluginServiceRegistry.start({
- coreStart,
- startPlugins,
- appUpdater: this.appUpdater,
- initContext: this.initContext,
- })
- );
-
const { expressions, presentationUtil } = startPlugins;
await presentationUtil.registerExpressionsLanguage(
Object.values(expressions.getFunctions())
@@ -154,7 +146,13 @@ export class CanvasPlugin
this.appUpdater
);
- const unmount = renderApp({ coreStart, startPlugins, params, canvasStore, pluginServices });
+ const unmount = renderApp({
+ coreStart,
+ startPlugins,
+ params,
+ canvasStore,
+ appUpdater: this.appUpdater,
+ });
return () => {
unmount();
@@ -190,6 +188,7 @@ export class CanvasPlugin
}
public start(coreStart: CoreStart, startPlugins: CanvasStartDeps) {
+ setKibanaServices(coreStart, startPlugins, this.initContext);
initLoadingIndicator(coreStart.http.addLoadingCountSource);
}
}
diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts
index ca66fa227e4eb0..e517f74030c87b 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts
+++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts
@@ -6,32 +6,30 @@
*/
import { useContext, useEffect } from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
-import { usePlatformService } from '../../../services';
import { WorkpadRoutingContext } from '..';
+import { coreServices } from '../../../services/kibana_services';
const fullscreenClass = 'canvas-isFullscreen';
export const useFullscreenPresentationHelper = () => {
const { isFullscreen } = useContext(WorkpadRoutingContext);
- const { setFullscreen } = usePlatformService();
-
useEffect(() => {
const body = document.querySelector('body');
const bodyClassList = body!.classList;
const hasFullscreenClass = bodyClassList.contains(fullscreenClass);
if (isFullscreen && !hasFullscreenClass) {
- setFullscreen(false);
+ coreServices.chrome.setIsVisible(false);
bodyClassList.add(fullscreenClass);
} else if (!isFullscreen && hasFullscreenClass) {
bodyClassList.remove(fullscreenClass);
- setFullscreen(true);
+ coreServices.chrome.setIsVisible(true);
}
- }, [isFullscreen, setFullscreen]);
+ }, [isFullscreen]);
// Remove fullscreen when component unmounts
useEffectOnce(() => () => {
- setFullscreen(true);
+ coreServices.chrome.setIsVisible(true);
document.querySelector('body')?.classList.remove(fullscreenClass);
});
};
diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx
index 0d73fe49601c82..1ba0dacd8c1432 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx
+++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx
@@ -7,6 +7,7 @@
import { renderHook } from '@testing-library/react-hooks';
import { useWorkpad } from './use_workpad';
+import { spacesService } from '../../../services/kibana_services';
const mockDispatch = jest.fn();
const mockSelector = jest.fn();
@@ -25,21 +26,22 @@ const workpadResponse = {
assets,
};
-// Mock the hooks and actions used by the UseWorkpad hook
+// Mock the hooks, actions, and services used by the UseWorkpad hook
jest.mock('react-redux', () => ({
useDispatch: () => mockDispatch,
useSelector: () => mockSelector,
}));
-jest.mock('../../../services', () => ({
- useWorkpadService: () => ({
- resolve: mockResolveWorkpad,
- }),
- usePlatformService: () => ({
- redirectLegacyUrl: mockRedirectLegacyUrl,
- }),
+jest.mock('../../../services/canvas_workpad_service', () => ({
+ getCanvasWorkpadService: () => {
+ return {
+ resolve: mockResolveWorkpad,
+ };
+ },
}));
+spacesService!.ui.redirectLegacyUrl = mockRedirectLegacyUrl;
+
jest.mock('../../../state/actions/workpad', () => ({
setWorkpad: (payload: any) => ({
type: 'setWorkpad',
diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts
index 61908d96828fd2..c02432477a840a 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts
+++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.ts
@@ -8,7 +8,6 @@
import { useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { useDispatch, useSelector } from 'react-redux';
-import { useWorkpadService, usePlatformService } from '../../../services';
import { getWorkpad } from '../../../state/selectors/workpad';
import { setWorkpad } from '../../../state/actions/workpad';
// @ts-expect-error
@@ -16,7 +15,11 @@ import { setAssets } from '../../../state/actions/assets';
// @ts-expect-error
import { setZoomScale } from '../../../state/actions/transient';
import { CanvasWorkpad } from '../../../../types';
-import type { ResolveWorkpadResponse } from '../../../services/workpad';
+import {
+ ResolveWorkpadResponse,
+ getCanvasWorkpadService,
+} from '../../../services/canvas_workpad_service';
+import { spacesService } from '../../../services/kibana_services';
const getWorkpadLabel = () =>
i18n.translate('xpack.canvas.workpadResolve.redirectLabel', {
@@ -32,9 +35,6 @@ export const useWorkpad = (
loadPages: boolean = true,
getRedirectPath: (workpadId: string) => string
): [CanvasWorkpad | undefined, string | Error | undefined] => {
- const workpadService = useWorkpadService();
- const workpadResolve = workpadService.resolve;
- const platformService = usePlatformService();
const dispatch = useDispatch();
const storedWorkpad = useSelector(getWorkpad);
const [error, setError] = useState(undefined);
@@ -47,14 +47,12 @@ export const useWorkpad = (
const {
workpad: { assets, ...workpad },
...resolveProps
- } = await workpadResolve(workpadId);
-
+ } = await getCanvasWorkpadService().resolve(workpadId);
setResolveInfo({ id: workpadId, ...resolveProps });
// If it's an alias match, we know we are going to redirect so don't even dispatch that we got the workpad
if (storedWorkpad.id !== workpadId && resolveProps.outcome !== 'aliasMatch') {
workpad.aliasId = resolveProps.aliasId;
-
dispatch(setAssets(assets));
dispatch(setWorkpad(workpad, { loadPages }));
dispatch(setZoomScale(1));
@@ -63,7 +61,7 @@ export const useWorkpad = (
setError(e as Error | string);
}
})();
- }, [workpadId, dispatch, setError, loadPages, workpadResolve, storedWorkpad.id]);
+ }, [workpadId, dispatch, setError, loadPages, storedWorkpad.id]);
useEffect(() => {
// If the resolved info is not for the current workpad id, bail out
@@ -75,16 +73,16 @@ export const useWorkpad = (
if (!resolveInfo) return;
const { aliasId, outcome, aliasPurpose } = resolveInfo;
- if (outcome === 'aliasMatch' && platformService.redirectLegacyUrl && aliasId) {
+ if (outcome === 'aliasMatch' && spacesService && aliasId) {
const redirectPath = getRedirectPath(aliasId);
- await platformService.redirectLegacyUrl({
+ await spacesService.ui.redirectLegacyUrl({
path: `#${redirectPath}`,
aliasPurpose,
objectNoun: getWorkpadLabel(),
});
}
})();
- }, [workpadId, resolveInfo, getRedirectPath, platformService]);
+ }, [workpadId, resolveInfo, getRedirectPath]);
return [storedWorkpad.id === workpadId ? storedWorkpad : undefined, error];
};
diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx
index 395cf774760fd2..3193ad3dd79e52 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx
+++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx
@@ -19,12 +19,17 @@ jest.mock('react-redux', () => ({
useSelector: (selector: any) => selector(mockGetState()),
}));
+jest.mock('../../../services/canvas_workpad_service', () => ({
+ getCanvasWorkpadService: () => {
+ return {
+ updateWorkpad: mockUpdateWorkpad,
+ updateAssets: mockUpdateAssets,
+ update: mockUpdate,
+ };
+ },
+}));
+
jest.mock('../../../services', () => ({
- useWorkpadService: () => ({
- updateWorkpad: mockUpdateWorkpad,
- updateAssets: mockUpdateAssets,
- update: mockUpdate,
- }),
useNotifyService: () => ({
error: mockNotifyError,
}),
diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts
index 45e21e59717ad9..f0f885c94eabc8 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts
+++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.ts
@@ -10,13 +10,10 @@ import { useSelector } from 'react-redux';
import { CanvasWorkpad, State } from '../../../../types';
import { getWorkpad } from '../../../state/selectors/workpad';
import { canUserWrite } from '../../../state/selectors/app';
-import { useWorkpadService, useNotifyService } from '../../../services';
import { notifyError } from '../../../lib/assets';
+import { getCanvasWorkpadService } from '../../../services/canvas_workpad_service';
export const useWorkpadPersist = () => {
- const service = useWorkpadService();
- const notifyService = useNotifyService();
-
// Watch for workpad state and then persist those changes
const [workpad, canWrite]: [CanvasWorkpad, boolean] = useSelector((state: State) => [
getWorkpad(state),
@@ -30,10 +27,12 @@ export const useWorkpadPersist = () => {
useEffect(() => {
if (canWrite) {
if (workpadChanged) {
- service.updateWorkpad(workpad.id, workpad).catch((err) => {
- notifyError(err, notifyService.error);
- });
+ getCanvasWorkpadService()
+ .updateWorkpad(workpad.id, workpad)
+ .catch((err) => {
+ notifyError(err);
+ });
}
}
- }, [service, workpad, workpadChanged, canWrite, notifyService.error]);
+ }, [workpad, workpadChanged, canWrite]);
};
diff --git a/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx b/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx
index 0dfb4dd8fbf783..bde100182788ef 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx
+++ b/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx
@@ -14,7 +14,7 @@ import { getWorkpad } from '../../state/selectors/workpad';
import { useFullscreenPresentationHelper } from './hooks/use_fullscreen_presentation_helper';
import { useAutoplayHelper } from './hooks/use_autoplay_helper';
import { useRefreshHelper } from './hooks/use_refresh_helper';
-import { usePlatformService } from '../../services';
+import { coreServices, spacesService } from '../../services/kibana_services';
const getWorkpadLabel = () =>
i18n.translate('xpack.canvas.workpadConflict.redirectLabel', {
@@ -22,7 +22,6 @@ const getWorkpadLabel = () =>
});
export const WorkpadPresentationHelper: FC> = ({ children }) => {
- const platformService = usePlatformService();
const workpad = useSelector(getWorkpad);
useFullscreenPresentationHelper();
useAutoplayHelper();
@@ -30,18 +29,18 @@ export const WorkpadPresentationHelper: FC> = ({ chil
const history = useHistory();
useEffect(() => {
- platformService.setBreadcrumbs([
+ coreServices.chrome.setBreadcrumbs([
getBaseBreadcrumb(history),
getWorkpadBreadcrumb({ name: workpad.name }),
]);
- }, [workpad.name, platformService, history]);
+ }, [workpad.name, history]);
useEffect(() => {
setDocTitle(workpad.name || getUntitledWorkpadLabel());
}, [workpad.name, workpad.id]);
const conflictElement = workpad.aliasId
- ? platformService.getLegacyUrlConflict?.({
+ ? spacesService?.ui.components.getLegacyUrlConflict({
objectNoun: getWorkpadLabel(),
currentObjectId: workpad.id,
otherObjectId: workpad.aliasId,
diff --git a/x-pack/plugins/canvas/public/services/canvas_custom_element_service.ts b/x-pack/plugins/canvas/public/services/canvas_custom_element_service.ts
new file mode 100644
index 00000000000000..1f0e13d2fbeaed
--- /dev/null
+++ b/x-pack/plugins/canvas/public/services/canvas_custom_element_service.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 { API_ROUTE_CUSTOM_ELEMENT } from '../../common/lib';
+import { CustomElement } from '../../types';
+import { coreServices } from './kibana_services';
+
+export interface CustomElementFindResponse {
+ total: number;
+ customElements: CustomElement[];
+}
+
+class CanvasCustomElementService {
+ public apiPath = `${API_ROUTE_CUSTOM_ELEMENT}`;
+
+ public async create(customElement: CustomElement) {
+ await coreServices.http.post(this.apiPath, {
+ body: JSON.stringify(customElement),
+ version: '1',
+ });
+ }
+
+ public async get(customElementId: string): Promise {
+ return await coreServices.http
+ .get<{ data: CustomElement }>(`${this.apiPath}/${customElementId}`, { version: '1' })
+ .then(({ data: element }) => element);
+ }
+
+ public async update(id: string, element: Partial) {
+ await coreServices.http.put(`${this.apiPath}/${id}`, {
+ body: JSON.stringify(element),
+ version: '1',
+ });
+ }
+
+ public async remove(id: string) {
+ await coreServices.http.delete(`${this.apiPath}/${id}`, { version: '1' });
+ }
+
+ public async find(searchTerm: string): Promise {
+ return await coreServices.http.get(`${this.apiPath}/find`, {
+ query: {
+ name: searchTerm,
+ perPage: 10000,
+ },
+ version: '1',
+ });
+ }
+}
+
+let canvasCustomElementService: CanvasCustomElementService;
+
+export const getCustomElementService = () => {
+ if (!canvasCustomElementService) {
+ canvasCustomElementService = new CanvasCustomElementService();
+ }
+ return canvasCustomElementService;
+};
diff --git a/x-pack/plugins/canvas/public/services/kibana/expressions.ts b/x-pack/plugins/canvas/public/services/canvas_expressions_service.ts
similarity index 65%
rename from x-pack/plugins/canvas/public/services/kibana/expressions.ts
rename to x-pack/plugins/canvas/public/services/canvas_expressions_service.ts
index 248cd462bea909..0621c3e89416c9 100644
--- a/x-pack/plugins/canvas/public/services/kibana/expressions.ts
+++ b/x-pack/plugins/canvas/public/services/canvas_expressions_service.ts
@@ -4,34 +4,29 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { fromExpression, getType } from '@kbn/interpreter';
import {
ExpressionAstExpression,
ExpressionExecutionParams,
ExpressionValue,
} from '@kbn/expressions-plugin/common';
+import { fromExpression, getType } from '@kbn/interpreter';
import { pluck } from 'rxjs';
-import { ExpressionsServiceStart } from '@kbn/expressions-plugin/public';
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { buildEmbeddableFilters } from '../../../common/lib/build_embeddable_filters';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasFiltersService } from './filters';
-import { CanvasNotifyService } from '../notify';
+import { buildEmbeddableFilters } from '../../common/lib/build_embeddable_filters';
+import { expressionsService } from './kibana_services';
+import { getCanvasNotifyService } from './canvas_notify_service';
+import { getCanvasFiltersService } from './canvas_filters_service';
interface Options {
castToRender?: boolean;
}
-export class ExpressionsService {
- private filters: CanvasFiltersService;
- private notify: CanvasNotifyService;
+class ExpressionsService {
+ private notifyService;
+ private filtersService;
- constructor(
- private readonly expressions: ExpressionsServiceStart,
- { filters, notify }: CanvasExpressionsServiceRequiredServices
- ) {
- this.filters = filters;
- this.notify = notify;
+ constructor() {
+ this.notifyService = getCanvasNotifyService();
+ this.filtersService = getCanvasFiltersService();
}
async interpretAst(
@@ -51,7 +46,7 @@ export class ExpressionsService {
input: ExpressionValue = null,
context?: ExpressionExecutionParams
): Promise {
- return await this.expressions
+ return await expressionsService
.execute(ast, input, { ...context, namespace: 'canvas' })
.getData()
.pipe(pluck('result'))
@@ -92,22 +87,22 @@ export class ExpressionsService {
throw new Error(`Ack! I don't know how to render a '${getType(renderable)}'`);
} catch (err) {
- this.notify.error(err);
+ this.notifyService.error(err);
throw err;
}
}
getRenderer(name: string) {
- return this.expressions.getRenderer(name);
+ return expressionsService.getRenderer(name);
}
getFunctions() {
- return this.expressions.getFunctions();
+ return expressionsService.getFunctions();
}
private async getFilters() {
- const filtersList = this.filters.getFilters();
- const context = this.filters.getFiltersContext();
+ const filtersList = this.filtersService.getFilters();
+ const context = this.filtersService.getFiltersContext();
const filterExpression = filtersList.join(' | ');
const filterAST = fromExpression(filterExpression);
return await this.interpretAstWithContext(filterAST, null, context);
@@ -122,19 +117,11 @@ export class ExpressionsService {
}
}
-export type CanvasExpressionsService = ExpressionsService;
-export interface CanvasExpressionsServiceRequiredServices {
- notify: CanvasNotifyService;
- filters: CanvasFiltersService;
-}
-
-export type CanvasExpressionsServiceFactory = KibanaPluginServiceFactory<
- CanvasExpressionsService,
- CanvasStartDeps,
- CanvasExpressionsServiceRequiredServices
->;
+let canvasExpressionsService: ExpressionsService;
-export const expressionsServiceFactory: CanvasExpressionsServiceFactory = (
- { startPlugins },
- requiredServices
-) => new ExpressionsService(startPlugins.expressions, requiredServices);
+export const getCanvasExpressionService = () => {
+ if (!canvasExpressionsService) {
+ canvasExpressionsService = new ExpressionsService();
+ }
+ return canvasExpressionsService;
+};
diff --git a/x-pack/plugins/canvas/public/services/kibana/filters.ts b/x-pack/plugins/canvas/public/services/canvas_filters_service.ts
similarity index 51%
rename from x-pack/plugins/canvas/public/services/kibana/filters.ts
rename to x-pack/plugins/canvas/public/services/canvas_filters_service.ts
index 793b073aaf2313..e678e9b30963fe 100644
--- a/x-pack/plugins/canvas/public/services/kibana/filters.ts
+++ b/x-pack/plugins/canvas/public/services/canvas_filters_service.ts
@@ -5,25 +5,21 @@
* 2.0.
*/
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
// @ts-expect-error untyped local
-import { getState, getStore } from '../../state/store';
-import { State } from '../../../types';
-import { getGlobalFilters, getWorkpadVariablesAsObject } from '../../state/selectors/workpad';
-import { CanvasStartDeps } from '../../plugin';
+import { getState, getStore } from '../state/store';
+import { State } from '../../types';
+import { getGlobalFilters, getWorkpadVariablesAsObject } from '../state/selectors/workpad';
// @ts-expect-error untyped local
-import { setFilter } from '../../state/actions/elements';
-
-export class FiltersService {
- constructor() {}
+import { setFilter } from '../state/actions/filters';
+class FiltersService {
getFilters(state: State = getState()) {
return getGlobalFilters(state);
}
updateFilter(filterId: string, filterExpression: string) {
const { dispatch } = getStore();
- dispatch(setFilter(filterExpression, filterId, true));
+ dispatch(setFilter(filterExpression, filterId));
}
getFiltersContext(state: State = getState()) {
@@ -32,11 +28,11 @@ export class FiltersService {
}
}
-export type CanvasFiltersService = FiltersService;
-
-export type CanvasFiltersServiceFactory = KibanaPluginServiceFactory<
- CanvasFiltersService,
- CanvasStartDeps
->;
+let canvasFiltersService: FiltersService;
-export const filtersServiceFactory: CanvasFiltersServiceFactory = () => new FiltersService();
+export const getCanvasFiltersService = () => {
+ if (!canvasFiltersService) {
+ canvasFiltersService = new FiltersService();
+ }
+ return canvasFiltersService;
+};
diff --git a/x-pack/plugins/canvas/public/services/kibana/notify.ts b/x-pack/plugins/canvas/public/services/canvas_notify_service.ts
similarity index 71%
rename from x-pack/plugins/canvas/public/services/kibana/notify.ts
rename to x-pack/plugins/canvas/public/services/canvas_notify_service.ts
index 8448a1d515fe4d..1f00190f3fff97 100644
--- a/x-pack/plugins/canvas/public/services/kibana/notify.ts
+++ b/x-pack/plugins/canvas/public/services/canvas_notify_service.ts
@@ -6,17 +6,10 @@
*/
import { get } from 'lodash';
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { ToastInputFields } from '@kbn/core/public';
-import { formatMsg } from '../../lib/format_msg';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasNotifyService } from '../notify';
-
-export type CanvasNotifyServiceFactory = KibanaPluginServiceFactory<
- CanvasNotifyService,
- CanvasStartDeps
->;
+import { formatMsg } from '../lib/format_msg';
+import { coreServices } from './kibana_services';
const getToast = (err: Error | string, opts: ToastInputFields = {}) => {
const errData = (get(err, 'response') || err) as Error | string;
@@ -36,8 +29,15 @@ const getToast = (err: Error | string, opts: ToastInputFields = {}) => {
};
};
-export const notifyServiceFactory: CanvasNotifyServiceFactory = ({ coreStart }) => {
- const toasts = coreStart.notifications.toasts;
+export interface CanvasNotifyService {
+ error: (err: string | Error, opts?: ToastInputFields) => void;
+ warning: (err: string | Error, opts?: ToastInputFields) => void;
+ info: (err: string | Error, opts?: ToastInputFields) => void;
+ success: (err: string | Error, opts?: ToastInputFields) => void;
+}
+
+export const getCanvasNotifyService = (): CanvasNotifyService => {
+ const toasts = coreServices.notifications.toasts;
return {
/*
diff --git a/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts b/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts
new file mode 100644
index 00000000000000..2672f6ef4a06e4
--- /dev/null
+++ b/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts
@@ -0,0 +1,200 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ResolvedSimpleSavedObject, SavedObject } from '@kbn/core/public';
+import {
+ API_ROUTE_SHAREABLE_ZIP,
+ API_ROUTE_TEMPLATES,
+ API_ROUTE_WORKPAD,
+ API_ROUTE_WORKPAD_ASSETS,
+ API_ROUTE_WORKPAD_STRUCTURES,
+ DEFAULT_WORKPAD_CSS,
+} from '../../common/lib';
+import type { CanvasRenderedWorkpad } from '../../shareable_runtime/types';
+import { CanvasTemplate, CanvasWorkpad } from '../../types';
+import { coreServices } from './kibana_services';
+
+export type FoundWorkpads = Array>;
+export type FoundWorkpad = FoundWorkpads[number];
+export interface WorkpadFindResponse {
+ total: number;
+ workpads: FoundWorkpads;
+}
+
+export interface TemplateFindResponse {
+ templates: CanvasTemplate[];
+}
+
+export interface ResolveWorkpadResponse {
+ workpad: CanvasWorkpad;
+ outcome: ResolvedSimpleSavedObject['outcome'];
+ aliasId?: ResolvedSimpleSavedObject['alias_target_id'];
+ aliasPurpose?: ResolvedSimpleSavedObject['alias_purpose'];
+}
+
+/*
+ Remove any top level keys from the workpad which will be rejected by validation
+*/
+const validKeys = [
+ '@created',
+ '@timestamp',
+ 'assets',
+ 'colors',
+ 'css',
+ 'variables',
+ 'height',
+ 'id',
+ 'isWriteable',
+ 'name',
+ 'page',
+ 'pages',
+ 'width',
+];
+
+const sanitizeWorkpad = function (workpad: CanvasWorkpad) {
+ const workpadKeys = Object.keys(workpad);
+
+ for (const key of workpadKeys) {
+ if (!validKeys.includes(key)) {
+ delete (workpad as { [key: string]: any })[key];
+ }
+ }
+
+ return workpad;
+};
+
+class CanvasWorkpadService {
+ private apiPath = `${API_ROUTE_WORKPAD}`;
+
+ public async get(id: string): Promise {
+ const workpad = await coreServices.http.get(`${this.apiPath}/${id}`, { version: '1' });
+
+ return { css: DEFAULT_WORKPAD_CSS, variables: [], ...workpad };
+ }
+
+ public async export(id: string) {
+ const workpad = await coreServices.http.get>(
+ `${this.apiPath}/export/${id}`,
+ { version: '1' }
+ );
+ const { attributes } = workpad;
+
+ return {
+ ...workpad,
+ attributes: {
+ ...attributes,
+ css: attributes.css ?? DEFAULT_WORKPAD_CSS,
+ variables: attributes.variables ?? [],
+ },
+ };
+ }
+
+ public async resolve(id: string): Promise {
+ const { workpad, ...resolveProps } = await coreServices.http.get(
+ `${this.apiPath}/resolve/${id}`,
+ { version: '1' }
+ );
+
+ return {
+ ...resolveProps,
+ workpad: {
+ // @ts-ignore: Shimming legacy workpads that might not have CSS
+ css: DEFAULT_WORKPAD_CSS,
+ // @ts-ignore: Shimming legacy workpads that might not have variables
+ variables: [],
+ ...workpad,
+ },
+ };
+ }
+
+ public async create(workpad: CanvasWorkpad): Promise {
+ return coreServices.http.post(this.apiPath, {
+ body: JSON.stringify({
+ ...sanitizeWorkpad({ ...workpad }),
+ assets: workpad.assets || {},
+ variables: workpad.variables || [],
+ }),
+ version: '1',
+ });
+ }
+
+ public async import(workpad: CanvasWorkpad): Promise {
+ return coreServices.http.post(`${this.apiPath}/import`, {
+ body: JSON.stringify({
+ ...sanitizeWorkpad({ ...workpad }),
+ assets: workpad.assets || {},
+ variables: workpad.variables || [],
+ }),
+ version: '1',
+ });
+ }
+
+ public async createFromTemplate(templateId: string): Promise {
+ return coreServices.http.post(this.apiPath, {
+ body: JSON.stringify({ templateId }),
+ version: '1',
+ });
+ }
+
+ public async findTemplates(): Promise {
+ return coreServices.http.get(API_ROUTE_TEMPLATES, { version: '1' });
+ }
+
+ public async find(searchTerm: string): Promise {
+ // TODO: this shouldn't be necessary. Check for usage.
+ const validSearchTerm = typeof searchTerm === 'string' && searchTerm.length > 0;
+
+ return coreServices.http.get(`${this.apiPath}/find`, {
+ query: {
+ perPage: 10000,
+ name: validSearchTerm ? searchTerm : '',
+ },
+ version: '1',
+ });
+ }
+
+ public async remove(id: string) {
+ coreServices.http.delete(`${this.apiPath}/${id}`, { version: '1' });
+ }
+
+ public async update(id: string, workpad: CanvasWorkpad) {
+ coreServices.http.put(`${this.apiPath}/${id}`, {
+ body: JSON.stringify({ ...sanitizeWorkpad({ ...workpad }) }),
+ version: '1',
+ });
+ }
+
+ public async updateWorkpad(id: string, workpad: CanvasWorkpad) {
+ coreServices.http.put(`${API_ROUTE_WORKPAD_STRUCTURES}/${id}`, {
+ body: JSON.stringify({ ...sanitizeWorkpad({ ...workpad }) }),
+ version: '1',
+ });
+ }
+
+ public async updateAssets(id: string, assets: CanvasWorkpad['assets']) {
+ coreServices.http.put(`${API_ROUTE_WORKPAD_ASSETS}/${id}`, {
+ body: JSON.stringify(assets),
+ version: '1',
+ });
+ }
+
+ public async getRuntimeZip(workpad: CanvasRenderedWorkpad): Promise {
+ return coreServices.http.post(API_ROUTE_SHAREABLE_ZIP, {
+ body: JSON.stringify(workpad),
+ version: '1',
+ });
+ }
+}
+
+let canvasWorkpadService: CanvasWorkpadService;
+
+export const getCanvasWorkpadService: () => CanvasWorkpadService = () => {
+ if (!canvasWorkpadService) {
+ canvasWorkpadService = new CanvasWorkpadService();
+ }
+ return canvasWorkpadService;
+};
diff --git a/x-pack/plugins/canvas/public/services/custom_element.ts b/x-pack/plugins/canvas/public/services/custom_element.ts
deleted file mode 100644
index 675a5a2f23c015..00000000000000
--- a/x-pack/plugins/canvas/public/services/custom_element.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { CustomElement } from '../../types';
-
-export interface CustomElementFindResponse {
- total: number;
- customElements: CustomElement[];
-}
-
-export interface CanvasCustomElementService {
- create: (customElement: CustomElement) => Promise;
- get: (customElementId: string) => Promise;
- update: (id: string, element: Partial) => Promise;
- remove: (id: string) => Promise;
- find: (searchTerm: string) => Promise;
-}
diff --git a/x-pack/plugins/canvas/public/services/data_views.ts b/x-pack/plugins/canvas/public/services/data_views.ts
deleted file mode 100644
index 86faa87bfaa597..00000000000000
--- a/x-pack/plugins/canvas/public/services/data_views.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { DataView } from '@kbn/data-views-plugin/common';
-
-export interface CanvasDataViewsService {
- getFields: (index: string) => Promise;
- getDataViews: () => Promise>>;
- getDefaultDataView: () => Promise | undefined>;
-}
diff --git a/x-pack/plugins/canvas/public/services/embeddables.ts b/x-pack/plugins/canvas/public/services/embeddables.ts
deleted file mode 100644
index b8f3d7d14b5e51..00000000000000
--- a/x-pack/plugins/canvas/public/services/embeddables.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- EmbeddableFactory,
- type EmbeddableStateTransfer,
- ReactEmbeddableSavedObject,
-} from '@kbn/embeddable-plugin/public';
-import { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
-
-export interface CanvasEmbeddablesService {
- reactEmbeddableRegistryHasKey: (key: string) => boolean;
- getReactEmbeddableSavedObjects: <
- TSavedObjectAttributes extends FinderAttributes
- >() => IterableIterator<[string, ReactEmbeddableSavedObject]>;
- getEmbeddableFactories: () => IterableIterator;
- getStateTransfer: () => EmbeddableStateTransfer;
-}
diff --git a/x-pack/plugins/canvas/public/services/expressions.ts b/x-pack/plugins/canvas/public/services/expressions.ts
deleted file mode 100644
index 456a1314bdfffc..00000000000000
--- a/x-pack/plugins/canvas/public/services/expressions.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export type { CanvasExpressionsService } from './kibana/expressions';
diff --git a/x-pack/plugins/canvas/public/services/filters.ts b/x-pack/plugins/canvas/public/services/filters.ts
deleted file mode 100644
index 1ced3d15f6e100..00000000000000
--- a/x-pack/plugins/canvas/public/services/filters.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export type { CanvasFiltersService } from './kibana/filters';
diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts
index dd035fcd3b1ad0..92b5f0ef3936a7 100644
--- a/x-pack/plugins/canvas/public/services/index.ts
+++ b/x-pack/plugins/canvas/public/services/index.ts
@@ -6,55 +6,11 @@
*/
export * from './legacy';
+import { useMemo } from 'react';
-import { PluginServices } from '@kbn/presentation-util-plugin/public';
+import { getCanvasNotifyService } from './canvas_notify_service';
-import { CanvasCustomElementService } from './custom_element';
-import { CanvasDataViewsService } from './data_views';
-import { CanvasEmbeddablesService } from './embeddables';
-import { CanvasExpressionsService } from './expressions';
-import { CanvasFiltersService } from './filters';
-import { CanvasLabsService } from './labs';
-import { CanvasNavLinkService } from './nav_link';
-import { CanvasNotifyService } from './notify';
-import { CanvasPlatformService } from './platform';
-import { CanvasReportingService } from './reporting';
-import { CanvasVisualizationsService } from './visualizations';
-import { CanvasWorkpadService } from './workpad';
-import { CanvasUiActionsService } from './ui_actions';
-
-export interface CanvasPluginServices {
- customElement: CanvasCustomElementService;
- dataViews: CanvasDataViewsService;
- embeddables: CanvasEmbeddablesService;
- expressions: CanvasExpressionsService;
- filters: CanvasFiltersService;
- labs: CanvasLabsService;
- navLink: CanvasNavLinkService;
- notify: CanvasNotifyService;
- platform: CanvasPlatformService;
- reporting: CanvasReportingService;
- visualizations: CanvasVisualizationsService;
- workpad: CanvasWorkpadService;
- uiActions: CanvasUiActionsService;
-}
-
-export const pluginServices = new PluginServices();
-
-export const useCustomElementService = () =>
- (() => pluginServices.getHooks().customElement.useService())();
-export const useDataViewsService = () => (() => pluginServices.getHooks().dataViews.useService())();
-export const useEmbeddablesService = () =>
- (() => pluginServices.getHooks().embeddables.useService())();
-export const useExpressionsService = () =>
- (() => pluginServices.getHooks().expressions.useService())();
-export const useFiltersService = () => (() => pluginServices.getHooks().filters.useService())();
-export const useLabsService = () => (() => pluginServices.getHooks().labs.useService())();
-export const useNavLinkService = () => (() => pluginServices.getHooks().navLink.useService())();
-export const useNotifyService = () => (() => pluginServices.getHooks().notify.useService())();
-export const usePlatformService = () => (() => pluginServices.getHooks().platform.useService())();
-export const useReportingService = () => (() => pluginServices.getHooks().reporting.useService())();
-export const useVisualizationsService = () =>
- (() => pluginServices.getHooks().visualizations.useService())();
-export const useWorkpadService = () => (() => pluginServices.getHooks().workpad.useService())();
-export const useUiActionsService = () => (() => pluginServices.getHooks().uiActions.useService())();
+export const useNotifyService = () => {
+ const canvasNotifyService = useMemo(() => getCanvasNotifyService(), []);
+ return canvasNotifyService;
+};
diff --git a/x-pack/plugins/canvas/public/services/kibana/custom_element.ts b/x-pack/plugins/canvas/public/services/kibana/custom_element.ts
deleted file mode 100644
index a548ccdc23b2f4..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/custom_element.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { API_ROUTE_CUSTOM_ELEMENT } from '../../../common/lib/constants';
-import { CustomElement } from '../../../types';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasCustomElementService } from '../custom_element';
-
-export type CanvasCustomElementServiceFactory = KibanaPluginServiceFactory<
- CanvasCustomElementService,
- CanvasStartDeps
->;
-
-export const customElementServiceFactory: CanvasCustomElementServiceFactory = ({ coreStart }) => {
- const { http } = coreStart;
- const apiPath = `${API_ROUTE_CUSTOM_ELEMENT}`;
-
- return {
- create: (customElement) =>
- http.post(apiPath, { body: JSON.stringify(customElement), version: '1' }),
- get: (customElementId) =>
- http
- .get<{ data: CustomElement }>(`${apiPath}/${customElementId}`, { version: '1' })
- .then(({ data: element }) => element),
- update: (id, element) =>
- http.put(`${apiPath}/${id}`, { body: JSON.stringify(element), version: '1' }),
- remove: (id) => http.delete(`${apiPath}/${id}`, { version: '1' }),
- find: async (name) => {
- return http.get(`${apiPath}/find`, {
- query: {
- name,
- perPage: 10000,
- },
- version: '1',
- });
- },
- };
-};
diff --git a/x-pack/plugins/canvas/public/services/kibana/data_views.ts b/x-pack/plugins/canvas/public/services/kibana/data_views.ts
deleted file mode 100644
index 27ec0bb1c5fc60..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/data_views.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { DataView } from '@kbn/data-views-plugin/public';
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { ErrorStrings } from '../../../i18n';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasDataViewsService } from '../data_views';
-import { CanvasNotifyService } from '../notify';
-
-const { esService: strings } = ErrorStrings;
-
-export type DataViewsServiceFactory = KibanaPluginServiceFactory<
- CanvasDataViewsService,
- CanvasStartDeps,
- {
- notify: CanvasNotifyService;
- }
->;
-
-export const dataViewsServiceFactory: DataViewsServiceFactory = ({ startPlugins }, { notify }) => ({
- getDataViews: async () => {
- try {
- const dataViews = await startPlugins.dataViews.getIdsWithTitle();
- return dataViews.map(({ id, name, title }) => ({ id, name, title } as DataView));
- } catch (e) {
- notify.error(e, { title: strings.getIndicesFetchErrorMessage() });
- }
-
- return [];
- },
- getFields: async (dataViewTitle: string) => {
- const dataView = await startPlugins.dataViews.create({ title: dataViewTitle });
-
- return dataView.fields
- .filter((field) => !field.name.startsWith('_'))
- .map((field) => field.name);
- },
- getDefaultDataView: async () => {
- const dataView = await startPlugins.dataViews.getDefaultDataView();
-
- return dataView
- ? { id: dataView.id, name: dataView.name, title: dataView.getIndexPattern() }
- : undefined;
- },
-});
diff --git a/x-pack/plugins/canvas/public/services/kibana/embeddables.ts b/x-pack/plugins/canvas/public/services/kibana/embeddables.ts
deleted file mode 100644
index 20f6f3feec8a4c..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/embeddables.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasEmbeddablesService } from '../embeddables';
-
-export type EmbeddablesServiceFactory = KibanaPluginServiceFactory<
- CanvasEmbeddablesService,
- CanvasStartDeps
->;
-
-export const embeddablesServiceFactory: EmbeddablesServiceFactory = ({ startPlugins }) => ({
- reactEmbeddableRegistryHasKey: startPlugins.embeddable.reactEmbeddableRegistryHasKey,
- getReactEmbeddableSavedObjects: startPlugins.embeddable.getReactEmbeddableSavedObjects,
- getEmbeddableFactories: startPlugins.embeddable.getEmbeddableFactories,
- getStateTransfer: startPlugins.embeddable.getStateTransfer,
-});
diff --git a/x-pack/plugins/canvas/public/services/kibana/index.ts b/x-pack/plugins/canvas/public/services/kibana/index.ts
deleted file mode 100644
index cab569133f70f5..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/index.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- PluginServiceProviders,
- PluginServiceProvider,
- PluginServiceRegistry,
- KibanaPluginServiceParams,
-} from '@kbn/presentation-util-plugin/public';
-
-import { CanvasPluginServices } from '..';
-import { CanvasStartDeps } from '../../plugin';
-import { customElementServiceFactory } from './custom_element';
-import { dataViewsServiceFactory } from './data_views';
-import { embeddablesServiceFactory } from './embeddables';
-import { expressionsServiceFactory } from './expressions';
-import { labsServiceFactory } from './labs';
-import { navLinkServiceFactory } from './nav_link';
-import { notifyServiceFactory } from './notify';
-import { platformServiceFactory } from './platform';
-import { reportingServiceFactory } from './reporting';
-import { visualizationsServiceFactory } from './visualizations';
-import { workpadServiceFactory } from './workpad';
-import { filtersServiceFactory } from './filters';
-import { uiActionsServiceFactory } from './ui_actions';
-
-export { customElementServiceFactory } from './custom_element';
-export { dataViewsServiceFactory } from './data_views';
-export { embeddablesServiceFactory } from './embeddables';
-export { expressionsServiceFactory } from './expressions';
-export { filtersServiceFactory } from './filters';
-export { labsServiceFactory } from './labs';
-export { notifyServiceFactory } from './notify';
-export { platformServiceFactory } from './platform';
-export { reportingServiceFactory } from './reporting';
-export { visualizationsServiceFactory } from './visualizations';
-export { workpadServiceFactory } from './workpad';
-export { uiActionsServiceFactory } from './ui_actions';
-
-export const pluginServiceProviders: PluginServiceProviders<
- CanvasPluginServices,
- KibanaPluginServiceParams
-> = {
- customElement: new PluginServiceProvider(customElementServiceFactory),
- dataViews: new PluginServiceProvider(dataViewsServiceFactory, ['notify']),
- embeddables: new PluginServiceProvider(embeddablesServiceFactory),
- expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']),
- filters: new PluginServiceProvider(filtersServiceFactory),
- labs: new PluginServiceProvider(labsServiceFactory),
- navLink: new PluginServiceProvider(navLinkServiceFactory),
- notify: new PluginServiceProvider(notifyServiceFactory),
- platform: new PluginServiceProvider(platformServiceFactory),
- reporting: new PluginServiceProvider(reportingServiceFactory),
- visualizations: new PluginServiceProvider(visualizationsServiceFactory),
- workpad: new PluginServiceProvider(workpadServiceFactory),
- uiActions: new PluginServiceProvider(uiActionsServiceFactory),
-};
-
-export const pluginServiceRegistry = new PluginServiceRegistry<
- CanvasPluginServices,
- KibanaPluginServiceParams
->(pluginServiceProviders);
diff --git a/x-pack/plugins/canvas/public/services/kibana/labs.ts b/x-pack/plugins/canvas/public/services/kibana/labs.ts
deleted file mode 100644
index 69613ae67fd85b..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/labs.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { projectIDs } from '@kbn/presentation-util-plugin/common';
-import { UI_SETTINGS } from '../../../common';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasLabsService } from '../labs';
-
-export type CanvasLabsServiceFactory = KibanaPluginServiceFactory<
- CanvasLabsService,
- CanvasStartDeps
->;
-
-export const labsServiceFactory: CanvasLabsServiceFactory = ({ startPlugins, coreStart }) => ({
- projectIDs,
- isLabsEnabled: () => coreStart.uiSettings.get(UI_SETTINGS.ENABLE_LABS_UI),
- ...startPlugins.presentationUtil.labsService,
-});
diff --git a/x-pack/plugins/canvas/public/services/kibana/nav_link.ts b/x-pack/plugins/canvas/public/services/kibana/nav_link.ts
deleted file mode 100644
index 8470c688f5b2b4..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/nav_link.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { SESSIONSTORAGE_LASTPATH } from '../../../common/lib/constants';
-import { getSessionStorage } from '../../lib/storage';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasNavLinkService } from '../nav_link';
-
-export type CanvasNavLinkServiceFactory = KibanaPluginServiceFactory<
- CanvasNavLinkService,
- CanvasStartDeps
->;
-
-export const navLinkServiceFactory: CanvasNavLinkServiceFactory = ({ coreStart, appUpdater }) => ({
- updatePath: (path: string) => {
- appUpdater?.next(() => ({
- defaultPath: `${path}`,
- }));
-
- getSessionStorage().set(`${SESSIONSTORAGE_LASTPATH}:${coreStart.http.basePath.get()}`, path);
- },
-});
diff --git a/x-pack/plugins/canvas/public/services/kibana/platform.ts b/x-pack/plugins/canvas/public/services/kibana/platform.ts
deleted file mode 100644
index 1518280ab0688f..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/platform.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasPlatformService } from '../platform';
-
-export type CanvaPlatformServiceFactory = KibanaPluginServiceFactory<
- CanvasPlatformService,
- CanvasStartDeps
->;
-
-export const platformServiceFactory: CanvaPlatformServiceFactory = ({
- coreStart,
- initContext,
- startPlugins,
-}) => {
- if (!initContext) {
- throw new Error('Canvas platform service requires init context');
- }
-
- return {
- getBasePath: coreStart.http.basePath.get,
- getBasePathInterface: () => coreStart.http.basePath,
- getElasticWebsiteUrl: () => coreStart.docLinks.ELASTIC_WEBSITE_URL,
- getDocLinkVersion: () => coreStart.docLinks.DOC_LINK_VERSION,
- getKibanaVersion: () => initContext.env.packageInfo.version,
- // TODO: is there a better type for this? The capabilities type allows for a Record,
- // though we don't do this. So this cast may be the best option.
- getHasWriteAccess: () => coreStart.application.capabilities.canvas.save as boolean,
- getUISetting: coreStart.uiSettings.get.bind(coreStart.uiSettings),
- hasHeaderBanner$: coreStart.chrome.hasHeaderBanner$,
- setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
- setRecentlyAccessed: coreStart.chrome.recentlyAccessed.add,
- setFullscreen: coreStart.chrome.setIsVisible,
- redirectLegacyUrl: startPlugins.spaces?.ui.redirectLegacyUrl,
- getLegacyUrlConflict: startPlugins.spaces?.ui.components.getLegacyUrlConflict,
- getUISettings: () => coreStart.uiSettings,
- getHttp: () => coreStart.http,
- getContentManagement: () => startPlugins.contentManagement,
- };
-};
diff --git a/x-pack/plugins/canvas/public/services/kibana/reporting.ts b/x-pack/plugins/canvas/public/services/kibana/reporting.ts
deleted file mode 100644
index 56b9b91e45fc69..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/reporting.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasReportingService } from '../reporting';
-
-export type CanvasReportingServiceFactory = KibanaPluginServiceFactory<
- CanvasReportingService,
- CanvasStartDeps
->;
-
-export const reportingServiceFactory: CanvasReportingServiceFactory = ({
- startPlugins,
- coreStart,
-}) => {
- const { reporting } = startPlugins;
-
- const reportingEnabled = () => ({
- getReportingPanelPDFComponent: () => reporting?.components.ReportingPanelPDFV2 || null,
- });
- const reportingDisabled = () => ({ getReportingPanelPDFComponent: () => null });
-
- if (!reporting) {
- // Reporting is not enabled
- return reportingDisabled();
- }
-
- if (reporting.usesUiCapabilities()) {
- if (coreStart.application.capabilities.canvas?.generatePdf === true) {
- // Canvas has declared Reporting as a subfeature with the `generatePdf` UI Capability
- return reportingEnabled();
- } else {
- return reportingDisabled();
- }
- }
-
- // Legacy/Deprecated: Reporting is enabled as an Elasticsearch feature
- return reportingEnabled();
-};
diff --git a/x-pack/plugins/canvas/public/services/kibana/ui_actions.ts b/x-pack/plugins/canvas/public/services/kibana/ui_actions.ts
deleted file mode 100644
index bb621206082d16..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/ui_actions.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasUiActionsService } from '../ui_actions';
-
-export type UiActionsServiceFactory = KibanaPluginServiceFactory<
- CanvasUiActionsService,
- CanvasStartDeps
->;
-
-export const uiActionsServiceFactory: UiActionsServiceFactory = ({ startPlugins }) => ({
- getTriggerCompatibleActions: startPlugins.uiActions.getTriggerCompatibleActions,
-});
diff --git a/x-pack/plugins/canvas/public/services/kibana/visualizations.ts b/x-pack/plugins/canvas/public/services/kibana/visualizations.ts
deleted file mode 100644
index 6f1728953ee8eb..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/visualizations.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasVisualizationsService } from '../visualizations';
-
-export type VisualizationsServiceFactory = KibanaPluginServiceFactory<
- CanvasVisualizationsService,
- CanvasStartDeps
->;
-
-export const visualizationsServiceFactory: VisualizationsServiceFactory = ({ startPlugins }) => ({
- showNewVisModal: startPlugins.visualizations.showNewVisModal,
- getByGroup: startPlugins.visualizations.getByGroup,
- getAliases: startPlugins.visualizations.getAliases,
-});
diff --git a/x-pack/plugins/canvas/public/services/kibana/workpad.ts b/x-pack/plugins/canvas/public/services/kibana/workpad.ts
deleted file mode 100644
index 3b70244440cc85..00000000000000
--- a/x-pack/plugins/canvas/public/services/kibana/workpad.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { SavedObject } from '@kbn/core/public';
-import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { CanvasStartDeps } from '../../plugin';
-import { CanvasWorkpadService, ResolveWorkpadResponse } from '../workpad';
-
-import {
- API_ROUTE_WORKPAD,
- DEFAULT_WORKPAD_CSS,
- API_ROUTE_TEMPLATES,
- API_ROUTE_WORKPAD_ASSETS,
- API_ROUTE_WORKPAD_STRUCTURES,
- API_ROUTE_SHAREABLE_ZIP,
-} from '../../../common/lib/constants';
-import { CanvasWorkpad } from '../../../types';
-
-export type CanvasWorkpadServiceFactory = KibanaPluginServiceFactory<
- CanvasWorkpadService,
- CanvasStartDeps
->;
-
-/*
- Remove any top level keys from the workpad which will be rejected by validation
-*/
-const validKeys = [
- '@created',
- '@timestamp',
- 'assets',
- 'colors',
- 'css',
- 'variables',
- 'height',
- 'id',
- 'isWriteable',
- 'name',
- 'page',
- 'pages',
- 'width',
-];
-
-const sanitizeWorkpad = function (workpad: CanvasWorkpad) {
- const workpadKeys = Object.keys(workpad);
-
- for (const key of workpadKeys) {
- if (!validKeys.includes(key)) {
- delete (workpad as { [key: string]: any })[key];
- }
- }
-
- return workpad;
-};
-
-export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, startPlugins }) => {
- const getApiPath = function () {
- return `${API_ROUTE_WORKPAD}`;
- };
-
- return {
- get: async (id: string) => {
- const workpad = await coreStart.http.get(`${getApiPath()}/${id}`, { version: '1' });
-
- return { css: DEFAULT_WORKPAD_CSS, variables: [], ...workpad };
- },
- export: async (id: string) => {
- const workpad = await coreStart.http.get>(
- `${getApiPath()}/export/${id}`,
- { version: '1' }
- );
- const { attributes } = workpad;
-
- return {
- ...workpad,
- attributes: {
- ...attributes,
- css: attributes.css ?? DEFAULT_WORKPAD_CSS,
- variables: attributes.variables ?? [],
- },
- };
- },
- resolve: async (id: string) => {
- const { workpad, ...resolveProps } = await coreStart.http.get(
- `${getApiPath()}/resolve/${id}`,
- { version: '1' }
- );
-
- return {
- ...resolveProps,
- workpad: {
- // @ts-ignore: Shimming legacy workpads that might not have CSS
- css: DEFAULT_WORKPAD_CSS,
- // @ts-ignore: Shimming legacy workpads that might not have variables
- variables: [],
- ...workpad,
- },
- };
- },
- create: (workpad: CanvasWorkpad) => {
- return coreStart.http.post(getApiPath(), {
- body: JSON.stringify({
- ...sanitizeWorkpad({ ...workpad }),
- assets: workpad.assets || {},
- variables: workpad.variables || [],
- }),
- version: '1',
- });
- },
- import: (workpad: CanvasWorkpad) =>
- coreStart.http.post(`${getApiPath()}/import`, {
- body: JSON.stringify({
- ...sanitizeWorkpad({ ...workpad }),
- assets: workpad.assets || {},
- variables: workpad.variables || [],
- }),
- version: '1',
- }),
- createFromTemplate: (templateId: string) => {
- return coreStart.http.post(getApiPath(), {
- body: JSON.stringify({ templateId }),
- version: '1',
- });
- },
- findTemplates: async () => coreStart.http.get(API_ROUTE_TEMPLATES, { version: '1' }),
- find: (searchTerm: string) => {
- // TODO: this shouldn't be necessary. Check for usage.
- const validSearchTerm = typeof searchTerm === 'string' && searchTerm.length > 0;
-
- return coreStart.http.get(`${getApiPath()}/find`, {
- query: {
- perPage: 10000,
- name: validSearchTerm ? searchTerm : '',
- },
- version: '1',
- });
- },
- remove: (id: string) => {
- return coreStart.http.delete(`${getApiPath()}/${id}`, { version: '1' });
- },
- update: (id, workpad) => {
- return coreStart.http.put(`${getApiPath()}/${id}`, {
- body: JSON.stringify({ ...sanitizeWorkpad({ ...workpad }) }),
- version: '1',
- });
- },
- updateWorkpad: (id, workpad) => {
- return coreStart.http.put(`${API_ROUTE_WORKPAD_STRUCTURES}/${id}`, {
- body: JSON.stringify({ ...sanitizeWorkpad({ ...workpad }) }),
- version: '1',
- });
- },
- updateAssets: (id, assets) => {
- return coreStart.http.put(`${API_ROUTE_WORKPAD_ASSETS}/${id}`, {
- body: JSON.stringify(assets),
- version: '1',
- });
- },
- getRuntimeZip: (workpad) => {
- return coreStart.http.post(API_ROUTE_SHAREABLE_ZIP, {
- body: JSON.stringify(workpad),
- version: '1',
- });
- },
- };
-};
diff --git a/x-pack/plugins/canvas/public/services/kibana_services.ts b/x-pack/plugins/canvas/public/services/kibana_services.ts
new file mode 100644
index 00000000000000..2b966e698a874d
--- /dev/null
+++ b/x-pack/plugins/canvas/public/services/kibana_services.ts
@@ -0,0 +1,76 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { BehaviorSubject } from 'rxjs';
+
+import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
+import type { CoreStart, PluginInitializerContext } from '@kbn/core/public';
+import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+import type { EmbeddableStart } from '@kbn/embeddable-plugin/public/plugin';
+import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
+import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
+import type { ReportingStart } from '@kbn/reporting-plugin/public';
+import type { SpacesApi } from '@kbn/spaces-plugin/public';
+import type { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
+import type { VisualizationsStart } from '@kbn/visualizations-plugin/public';
+
+import type { CanvasStartDeps } from '../plugin';
+
+export let kibanaVersion: string;
+
+export let coreServices: CoreStart;
+export let contentManagementService: ContentManagementPublicStart;
+export let dataService: DataPublicPluginStart;
+export let dataViewsService: DataViewsPublicPluginStart;
+export let embeddableService: EmbeddableStart;
+export let expressionsService: ExpressionsStart;
+export let presentationUtilService: PresentationUtilPluginStart;
+export let reportingService: ReportingStart | undefined;
+export let spacesService: SpacesApi | undefined;
+export let uiActionsService: UiActionsPublicStart;
+export let visualizationsService: VisualizationsStart;
+
+const servicesReady$ = new BehaviorSubject(false);
+
+export const setKibanaServices = (
+ kibanaCore: CoreStart,
+ deps: CanvasStartDeps,
+ initContext: PluginInitializerContext
+) => {
+ kibanaVersion = initContext.env.packageInfo.version;
+
+ coreServices = kibanaCore;
+ contentManagementService = deps.contentManagement;
+ dataService = deps.data;
+ dataViewsService = deps.dataViews;
+ embeddableService = deps.embeddable;
+ expressionsService = deps.expressions;
+ presentationUtilService = deps.presentationUtil;
+ reportingService = Boolean(
+ deps.reporting?.usesUiCapabilities() && !kibanaCore.application.capabilities.canvas?.generatePdf
+ )
+ ? undefined
+ : deps.reporting;
+ spacesService = deps.spaces;
+ uiActionsService = deps.uiActions;
+ visualizationsService = deps.visualizations;
+
+ servicesReady$.next(true);
+};
+
+export const untilPluginStartServicesReady = () => {
+ if (servicesReady$.value) return Promise.resolve();
+ return new Promise((resolve) => {
+ const subscription = servicesReady$.subscribe((isInitialized) => {
+ if (isInitialized) {
+ subscription.unsubscribe();
+ resolve();
+ }
+ });
+ });
+};
diff --git a/x-pack/plugins/canvas/public/services/labs.ts b/x-pack/plugins/canvas/public/services/labs.ts
deleted file mode 100644
index 0f20e25f31d60f..00000000000000
--- a/x-pack/plugins/canvas/public/services/labs.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PresentationLabsService } from '@kbn/presentation-util-plugin/public';
-import { projectIDs } from '@kbn/presentation-util-plugin/common';
-
-export interface CanvasLabsService extends PresentationLabsService {
- projectIDs: typeof projectIDs;
- isLabsEnabled: () => boolean;
-}
diff --git a/x-pack/plugins/canvas/public/services/legacy/reporting.ts b/x-pack/plugins/canvas/public/services/legacy/reporting.ts
deleted file mode 100644
index 411a892baed295..00000000000000
--- a/x-pack/plugins/canvas/public/services/legacy/reporting.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ReportingStart } from '@kbn/reporting-plugin/public';
-import { CanvasServiceFactory } from '.';
-
-export interface ReportingService {
- start?: ReportingStart;
-}
-
-export const reportingServiceFactory: CanvasServiceFactory = (
- _coreSetup,
- coreStart,
- _setupPlugins,
- startPlugins
-): ReportingService => {
- const { reporting } = startPlugins;
-
- const reportingEnabled = () => ({ start: reporting });
- const reportingDisabled = () => ({ start: undefined });
-
- if (!reporting) {
- // Reporting is not enabled
- return reportingDisabled();
- }
-
- if (reporting.usesUiCapabilities()) {
- if (coreStart.application.capabilities.canvas?.generatePdf === true) {
- // Canvas has declared Reporting as a subfeature with the `generatePdf` UI Capability
- return reportingEnabled();
- } else {
- return reportingDisabled();
- }
- }
-
- // Legacy/Deprecated: Reporting is enabled as an Elasticsearch feature
- return reportingEnabled();
-};
diff --git a/x-pack/plugins/canvas/public/services/mocks.ts b/x-pack/plugins/canvas/public/services/mocks.ts
new file mode 100644
index 00000000000000..c35d8834253be4
--- /dev/null
+++ b/x-pack/plugins/canvas/public/services/mocks.ts
@@ -0,0 +1,125 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import moment from 'moment';
+
+import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
+import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks';
+import { CoreStart } from '@kbn/core/public';
+import { coreMock } from '@kbn/core/public/mocks';
+import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
+import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
+import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
+import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
+import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks';
+import { presentationUtilPluginMock } from '@kbn/presentation-util-plugin/public/mocks';
+import { reportingPluginMock } from '@kbn/reporting-plugin/public/mocks';
+import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks';
+import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
+import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks';
+
+import { setKibanaServices } from './kibana_services';
+import { getId } from '../lib/get_id';
+// @ts-expect-error
+import { getDefaultWorkpad } from '../state/defaults';
+
+const setDefaultPresentationUtilCapabilities = (core: CoreStart) => {
+ core.application.capabilities = {
+ ...core.application.capabilities,
+ dashboard: {
+ show: true,
+ createNew: true,
+ },
+ visualize: {
+ save: true,
+ },
+ advancedSettings: {
+ save: true,
+ },
+ };
+};
+
+export const setStubKibanaServices = () => {
+ const core: CoreStart = coreMock.createStart();
+
+ setDefaultPresentationUtilCapabilities(core);
+ setKibanaServices(
+ core,
+ {
+ charts: chartPluginMock.createStartContract(),
+ contentManagement: contentManagementMock.createStartContract(),
+ data: dataPluginMock.createStartContract(),
+ dataViews: dataViewPluginMocks.createStartContract(),
+ embeddable: embeddablePluginMock.createStartContract(),
+ expressions: expressionsPluginMock.createStartContract(),
+ inspector: inspectorPluginMock.createStartContract(),
+ presentationUtil: presentationUtilPluginMock.createStartContract(),
+ reporting: reportingPluginMock.createStartContract(),
+ spaces: spacesPluginMock.createStartContract(),
+ uiActions: uiActionsPluginMock.createStartContract(),
+ visualizations: visualizationsPluginMock.createStartContract(),
+ },
+ coreMock.createPluginInitializerContext()
+ );
+};
+
+const TIMEOUT = 500;
+
+export const getSomeWorkpads = (count = 3, useStaticData = false) => {
+ if (useStaticData) {
+ const DAY = 86400000;
+ const JAN_1_2000 = 946684800000;
+
+ const workpads = [];
+ for (let i = 0; i < count; i++) {
+ workpads[i] = {
+ ...getDefaultWorkpad(),
+ name: `Workpad ${i}`,
+ id: `workpad-${i}`,
+ '@created': moment(JAN_1_2000 + DAY * i).toDate(),
+ '@timestamp': moment(JAN_1_2000 + DAY * (i + 1)).toDate(),
+ };
+ }
+ return workpads;
+ } else {
+ return Array.from({ length: count }, () => ({
+ '@created': getRandomDate(
+ moment().subtract(3, 'days').toDate(),
+ moment().subtract(10, 'days').toDate()
+ ),
+ '@timestamp': getRandomDate(),
+ id: getId('workpad'),
+ name: getRandomName(),
+ }));
+ }
+};
+
+export const findSomeWorkpads =
+ (count = 3, useStaticData = false, timeout = TIMEOUT) =>
+ (_term: string) => {
+ return Promise.resolve()
+ .then(promiseTimeout(timeout))
+ .then(() => ({
+ total: count,
+ workpads: getSomeWorkpads(count, useStaticData),
+ }));
+ };
+
+const promiseTimeout = (time: number) => () => new Promise((resolve) => setTimeout(resolve, time));
+
+const getRandomName = () => {
+ const lorem =
+ 'Lorem ipsum dolor sit amet consectetur adipiscing elit Fusce lobortis aliquet arcu ut turpis duis'.split(
+ ' '
+ );
+ return [1, 2, 3].map(() => lorem[Math.floor(Math.random() * lorem.length)]).join(' ');
+};
+
+const getRandomDate = (
+ start: Date = moment().toDate(),
+ end: Date = moment().subtract(7, 'days').toDate()
+) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString();
diff --git a/x-pack/plugins/canvas/public/services/notify.ts b/x-pack/plugins/canvas/public/services/notify.ts
deleted file mode 100644
index 83e7b4e71ee725..00000000000000
--- a/x-pack/plugins/canvas/public/services/notify.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ToastInputFields } from '@kbn/core/public';
-
-export interface CanvasNotifyService {
- error: (err: string | Error, opts?: ToastInputFields) => void;
- warning: (err: string | Error, opts?: ToastInputFields) => void;
- info: (err: string | Error, opts?: ToastInputFields) => void;
- success: (err: string | Error, opts?: ToastInputFields) => void;
-}
diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts
deleted file mode 100644
index 555f8643fff9bd..00000000000000
--- a/x-pack/plugins/canvas/public/services/platform.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { Observable } from 'rxjs';
-import {
- IUiSettingsClient,
- ChromeBreadcrumb,
- IBasePath,
- ChromeStart,
- HttpStart,
-} from '@kbn/core/public';
-
-import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
-import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
-
-export interface CanvasPlatformService {
- getBasePath: () => string;
- getBasePathInterface: () => IBasePath;
- getDocLinkVersion: () => string;
- getElasticWebsiteUrl: () => string;
- getKibanaVersion: () => string;
- getHasWriteAccess: () => boolean;
- getUISetting: (key: string, defaultValue?: any) => any;
- hasHeaderBanner$: () => Observable;
- setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void;
- setRecentlyAccessed: (link: string, label: string, id: string) => void;
- setFullscreen: ChromeStart['setIsVisible'];
- redirectLegacyUrl?: SpacesPluginStart['ui']['redirectLegacyUrl'];
- getLegacyUrlConflict?: SpacesPluginStart['ui']['components']['getLegacyUrlConflict'];
- getUISettings: () => IUiSettingsClient;
- getHttp: () => HttpStart;
- getContentManagement: () => ContentManagementPublicStart;
-}
diff --git a/x-pack/plugins/canvas/public/services/reporting.ts b/x-pack/plugins/canvas/public/services/reporting.ts
deleted file mode 100644
index 71d90421eafa8a..00000000000000
--- a/x-pack/plugins/canvas/public/services/reporting.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ReportingStart } from '@kbn/reporting-plugin/public';
-
-type ReportingPanelPDFComponent = ReportingStart['components']['ReportingPanelPDFV2'];
-export interface CanvasReportingService {
- getReportingPanelPDFComponent: () => ReportingPanelPDFComponent | null;
-}
diff --git a/x-pack/plugins/canvas/public/services/storybook/index.ts b/x-pack/plugins/canvas/public/services/storybook/index.ts
deleted file mode 100644
index 52cad460662b2d..00000000000000
--- a/x-pack/plugins/canvas/public/services/storybook/index.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import {
- PluginServiceProviders,
- PluginServiceProvider,
-} from '@kbn/presentation-util-plugin/public';
-
-import { CanvasPluginServices } from '..';
-import { pluginServiceProviders as stubProviders } from '../stubs';
-import { workpadServiceFactory } from './workpad';
-import { notifyServiceFactory } from './notify';
-
-export interface StorybookParams {
- hasTemplates?: boolean;
- useStaticData?: boolean;
- workpadCount?: number;
-}
-
-export const pluginServiceProviders: PluginServiceProviders =
- {
- ...stubProviders,
- workpad: new PluginServiceProvider(workpadServiceFactory),
- notify: new PluginServiceProvider(notifyServiceFactory),
- };
-
-export const argTypes = {
- hasTemplates: {
- name: 'Has templates?',
- type: {
- name: 'boolean',
- },
- defaultValue: true,
- control: {
- type: 'boolean',
- },
- },
- useStaticData: {
- name: 'Use static data?',
- type: {
- name: 'boolean',
- },
- defaultValue: false,
- control: {
- type: 'boolean',
- },
- },
- workpadCount: {
- name: 'Number of workpads',
- type: { name: 'number' },
- defaultValue: 5,
- control: {
- type: 'range',
- },
- },
-};
diff --git a/x-pack/plugins/canvas/public/services/storybook/notify.ts b/x-pack/plugins/canvas/public/services/storybook/notify.ts
deleted file mode 100644
index f6f97c42a17416..00000000000000
--- a/x-pack/plugins/canvas/public/services/storybook/notify.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { action } from '@storybook/addon-actions';
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { StorybookParams } from '.';
-import { CanvasNotifyService } from '../notify';
-
-type CanvasNotifyServiceFactory = PluginServiceFactory;
-
-export const notifyServiceFactory: CanvasNotifyServiceFactory = () => ({
- success: (message) => action(`success: ${message}`)(),
- error: (message) => action(`error: ${message}`)(),
- info: (message) => action(`info: ${message}`)(),
- warning: (message) => action(`warning: ${message}`)(),
-});
diff --git a/x-pack/plugins/canvas/public/services/storybook/workpad.ts b/x-pack/plugins/canvas/public/services/storybook/workpad.ts
deleted file mode 100644
index 9f40ccccafb3d5..00000000000000
--- a/x-pack/plugins/canvas/public/services/storybook/workpad.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import moment from 'moment';
-
-import { action } from '@storybook/addon-actions';
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { getId } from '../../lib/get_id';
-// @ts-expect-error
-import { getDefaultWorkpad } from '../../state/defaults';
-
-import { StorybookParams } from '.';
-import { CanvasWorkpadService } from '../workpad';
-
-import * as stubs from '../stubs/workpad';
-
-export {
- findNoTemplates,
- findNoWorkpads,
- findSomeTemplates,
- getNoTemplates,
- getSomeTemplates,
-} from '../stubs/workpad';
-
-type CanvasWorkpadServiceFactory = PluginServiceFactory;
-
-const TIMEOUT = 500;
-const promiseTimeout = (time: number) => () => new Promise((resolve) => setTimeout(resolve, time));
-
-const { findNoTemplates, findNoWorkpads, findSomeTemplates, importWorkpad } = stubs;
-
-const getRandomName = () => {
- const lorem =
- 'Lorem ipsum dolor sit amet consectetur adipiscing elit Fusce lobortis aliquet arcu ut turpis duis'.split(
- ' '
- );
- return [1, 2, 3].map(() => lorem[Math.floor(Math.random() * lorem.length)]).join(' ');
-};
-
-const getRandomDate = (
- start: Date = moment().toDate(),
- end: Date = moment().subtract(7, 'days').toDate()
-) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString();
-
-export const getSomeWorkpads = (count = 3) =>
- Array.from({ length: count }, () => ({
- '@created': getRandomDate(
- moment().subtract(3, 'days').toDate(),
- moment().subtract(10, 'days').toDate()
- ),
- '@timestamp': getRandomDate(),
- id: getId('workpad'),
- name: getRandomName(),
- }));
-
-export const findSomeWorkpads =
- (count = 3, useStaticData = false, timeout = TIMEOUT) =>
- (_term: string) => {
- return Promise.resolve()
- .then(promiseTimeout(timeout))
- .then(() => ({
- total: count,
- workpads: useStaticData ? stubs.getSomeWorkpads(count) : getSomeWorkpads(count),
- }));
- };
-
-export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({
- workpadCount,
- hasTemplates,
- useStaticData,
-}) => ({
- get: (id: string) => {
- action('workpadService.get')(id);
- return Promise.resolve({ ...getDefaultWorkpad(), id });
- },
- resolve: (id: string) => {
- action('workpadService.resolve')(id);
- return Promise.resolve({ outcome: 'exactMatch', workpad: { ...getDefaultWorkpad(), id } });
- },
- findTemplates: () => {
- action('workpadService.findTemplates')();
- return (hasTemplates ? findSomeTemplates() : findNoTemplates())();
- },
- import: (workpad) => {
- action('workpadService.import')(workpad);
- return importWorkpad(workpad);
- },
- create: (workpad) => {
- action('workpadService.create')(workpad);
- return Promise.resolve(workpad);
- },
- createFromTemplate: (templateId: string) => {
- action('workpadService.createFromTemplate')(templateId);
- return Promise.resolve(getDefaultWorkpad());
- },
- find: (term: string) => {
- action('workpadService.find')(term);
- return (workpadCount ? findSomeWorkpads(workpadCount, useStaticData) : findNoWorkpads())(term);
- },
- remove: (id: string) => {
- action('workpadService.remove')(id);
- return Promise.resolve();
- },
- update: (id, workpad) => {
- action('worpadService.update')(workpad, id);
- return Promise.resolve();
- },
- updateWorkpad: (id, workpad) => {
- action('workpadService.updateWorkpad')(workpad, id);
- return Promise.resolve();
- },
- updateAssets: (id, assets) => {
- action('workpadService.updateAssets')(assets, id);
- return Promise.resolve();
- },
- getRuntimeZip: (workpad) =>
- Promise.resolve(new Blob([JSON.stringify(workpad)], { type: 'application/json' })),
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/custom_element.ts b/x-pack/plugins/canvas/public/services/stubs/custom_element.ts
deleted file mode 100644
index 551c06ef4b65ee..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/custom_element.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasCustomElementService } from '../custom_element';
-
-type CanvasCustomElementServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const customElementServiceFactory: CanvasCustomElementServiceFactory = () => ({
- create: noop,
- find: noop,
- get: noop,
- remove: noop,
- update: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/data_views.ts b/x-pack/plugins/canvas/public/services/stubs/data_views.ts
deleted file mode 100644
index 1b1227dba4d3fd..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/data_views.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasDataViewsService } from '../data_views';
-
-type DataViewsServiceFactory = PluginServiceFactory;
-
-export const dataViewsServiceFactory: DataViewsServiceFactory = () => ({
- getDataViews: () =>
- Promise.resolve([
- { id: 'dataview1', title: 'dataview1', name: 'Data view 1' },
- { id: 'dataview2', title: 'dataview2', name: 'Data view 2' },
- ]),
- getFields: () => Promise.resolve(['field1', 'field2']),
- getDefaultDataView: () =>
- Promise.resolve({
- id: 'defaultDataViewId',
- title: 'defaultDataView',
- name: 'Default data view',
- }),
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/embeddables.ts b/x-pack/plugins/canvas/public/services/stubs/embeddables.ts
deleted file mode 100644
index 95131efec2bde9..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/embeddables.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasEmbeddablesService } from '../embeddables';
-
-type EmbeddablesServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const embeddablesServiceFactory: EmbeddablesServiceFactory = () => ({
- reactEmbeddableRegistryHasKey: noop,
- getReactEmbeddableSavedObjects: noop,
- getEmbeddableFactories: noop,
- getStateTransfer: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/expressions.ts b/x-pack/plugins/canvas/public/services/stubs/expressions.ts
deleted file mode 100644
index 177eb1e50d75fb..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/expressions.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { AnyExpressionRenderDefinition } from '@kbn/expressions-plugin/common';
-import { plugin } from '@kbn/expressions-plugin/public';
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { functions as functionDefinitions } from '../../../canvas_plugin_src/functions/common';
-import { renderFunctions } from '../../../canvas_plugin_src/renderers/core';
-import {
- CanvasExpressionsService,
- CanvasExpressionsServiceRequiredServices,
- ExpressionsService,
-} from '../kibana/expressions';
-
-type CanvasExpressionsServiceFactory = PluginServiceFactory<
- CanvasExpressionsService,
- {},
- CanvasExpressionsServiceRequiredServices
->;
-
-export const expressionsServiceFactory: CanvasExpressionsServiceFactory = (
- params,
- requiredServices
-) => {
- const placeholder = {} as any;
- const expressionsPlugin = plugin(placeholder);
- const setup = expressionsPlugin.setup(placeholder);
- const fork = setup.fork('canvas');
- const expressionsService = fork.setup();
-
- functionDefinitions.forEach((fn) => expressionsService.registerFunction(fn));
- renderFunctions.forEach((fn) => {
- setup.registerRenderer(fn as unknown as AnyExpressionRenderDefinition);
- });
-
- return new ExpressionsService(fork.start(), requiredServices);
-};
diff --git a/x-pack/plugins/canvas/public/services/stubs/filters.ts b/x-pack/plugins/canvas/public/services/stubs/filters.ts
deleted file mode 100644
index 19d237bb9c390a..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/filters.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasFiltersService } from '../filters';
-
-export type CanvasFiltersServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const filtersServiceFactory: CanvasFiltersServiceFactory = () => ({
- getFilters: () => [
- 'exactly value="machine-learning" column="project1" filterGroup="Group 1"',
- 'exactly value="kibana" column="project2" filterGroup="Group 1"',
- 'time column="@timestamp1" from="2021-11-02 17:13:18" to="2021-11-09 17:13:18" filterGroup="Some group"',
- ],
- updateFilter: noop,
- getFiltersContext: () => ({ variables: {} }),
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/index.ts b/x-pack/plugins/canvas/public/services/stubs/index.ts
deleted file mode 100644
index c129441b078258..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-export * from '../legacy/stubs';
-
-import {
- PluginServiceProviders,
- PluginServiceProvider,
- PluginServiceRegistry,
-} from '@kbn/presentation-util-plugin/public';
-
-import { CanvasPluginServices } from '..';
-import { customElementServiceFactory } from './custom_element';
-import { dataViewsServiceFactory } from './data_views';
-import { embeddablesServiceFactory } from './embeddables';
-import { expressionsServiceFactory } from './expressions';
-import { labsServiceFactory } from './labs';
-import { navLinkServiceFactory } from './nav_link';
-import { notifyServiceFactory } from './notify';
-import { platformServiceFactory } from './platform';
-import { reportingServiceFactory } from './reporting';
-import { visualizationsServiceFactory } from './visualizations';
-import { workpadServiceFactory } from './workpad';
-import { filtersServiceFactory } from './filters';
-import { uiActionsServiceFactory } from './ui_actions';
-
-export { customElementServiceFactory } from './custom_element';
-export { dataViewsServiceFactory } from './data_views';
-export { expressionsServiceFactory } from './expressions';
-export { filtersServiceFactory } from './filters';
-export { labsServiceFactory } from './labs';
-export { navLinkServiceFactory } from './nav_link';
-export { notifyServiceFactory } from './notify';
-export { platformServiceFactory } from './platform';
-export { reportingServiceFactory } from './reporting';
-export { visualizationsServiceFactory } from './visualizations';
-export { workpadServiceFactory } from './workpad';
-
-export const pluginServiceProviders: PluginServiceProviders = {
- customElement: new PluginServiceProvider(customElementServiceFactory),
- dataViews: new PluginServiceProvider(dataViewsServiceFactory),
- embeddables: new PluginServiceProvider(embeddablesServiceFactory),
- expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']),
- filters: new PluginServiceProvider(filtersServiceFactory),
- labs: new PluginServiceProvider(labsServiceFactory),
- navLink: new PluginServiceProvider(navLinkServiceFactory),
- notify: new PluginServiceProvider(notifyServiceFactory),
- platform: new PluginServiceProvider(platformServiceFactory),
- reporting: new PluginServiceProvider(reportingServiceFactory),
- visualizations: new PluginServiceProvider(visualizationsServiceFactory),
- workpad: new PluginServiceProvider(workpadServiceFactory),
- uiActions: new PluginServiceProvider(uiActionsServiceFactory),
-};
-
-export const pluginServiceRegistry = new PluginServiceRegistry(
- pluginServiceProviders
-);
diff --git a/x-pack/plugins/canvas/public/services/stubs/labs.ts b/x-pack/plugins/canvas/public/services/stubs/labs.ts
deleted file mode 100644
index d563126a15da33..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/labs.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { projectIDs } from '@kbn/presentation-util-plugin/common';
-import { CanvasLabsService } from '../labs';
-
-type CanvasLabsServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const labsServiceFactory: CanvasLabsServiceFactory = () => ({
- getProject: noop,
- getProjects: noop,
- getProjectIDs: () => projectIDs,
- isProjectEnabled: () => false,
- isLabsEnabled: () => true,
- projectIDs,
- reset: noop,
- setProjectStatus: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/nav_link.ts b/x-pack/plugins/canvas/public/services/stubs/nav_link.ts
deleted file mode 100644
index becbdebcd63287..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/nav_link.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasNavLinkService } from '../nav_link';
-
-type CanvasNavLinkServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const navLinkServiceFactory: CanvasNavLinkServiceFactory = () => ({
- updatePath: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/notify.ts b/x-pack/plugins/canvas/public/services/stubs/notify.ts
deleted file mode 100644
index 2406afe3ca8a2b..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/notify.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { CanvasNotifyService } from '../notify';
-
-type CanvasNotifyServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const notifyServiceFactory: CanvasNotifyServiceFactory = () => ({
- error: noop,
- info: noop,
- success: noop,
- warning: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts
deleted file mode 100644
index 07268100752519..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/platform.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { CanvasPlatformService } from '../platform';
-
-type CanvasPlatformServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-const uiSettings: Record = {
- dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
-};
-
-const getUISetting = (setting: string) => uiSettings[setting];
-
-export const platformServiceFactory: CanvasPlatformServiceFactory = () => ({
- getBasePath: () => '/base/path',
- getBasePathInterface: noop,
- getDocLinkVersion: () => 'docLinkVersion',
- getElasticWebsiteUrl: () => 'https://elastic.co',
- getKibanaVersion: () => 'kibanaVersion',
- getHasWriteAccess: () => true,
- getUISetting,
- hasHeaderBanner$: noop,
- setBreadcrumbs: noop,
- setRecentlyAccessed: noop,
- getUISettings: noop,
- setFullscreen: noop,
- redirectLegacyUrl: noop,
- getLegacyUrlConflict: undefined,
- getHttp: noop,
- getContentManagement: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/reporting.tsx b/x-pack/plugins/canvas/public/services/stubs/reporting.tsx
deleted file mode 100644
index 71c376efbaba96..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/reporting.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-import { CanvasReportingService } from '../reporting';
-
-type CanvasReportingServiceFactory = PluginServiceFactory;
-
-export const reportingServiceFactory: CanvasReportingServiceFactory = () => ({
- getReportingPanelPDFComponent: () => () => Reporting Panel PDF
,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/ui_actions.ts b/x-pack/plugins/canvas/public/services/stubs/ui_actions.ts
deleted file mode 100644
index 84039e0888be8e..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/ui_actions.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks';
-import { CanvasUiActionsService } from '../ui_actions';
-
-type UiActionsServiceFactory = PluginServiceFactory;
-
-export const uiActionsServiceFactory: UiActionsServiceFactory = () => {
- const pluginMock = uiActionsPluginMock.createStartContract();
- return { getTriggerCompatibleActions: pluginMock.getTriggerCompatibleActions };
-};
diff --git a/x-pack/plugins/canvas/public/services/stubs/visualizations.ts b/x-pack/plugins/canvas/public/services/stubs/visualizations.ts
deleted file mode 100644
index 812287bf078fb0..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/visualizations.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-import { CanvasVisualizationsService } from '../visualizations';
-
-type VisualizationsServiceFactory = PluginServiceFactory;
-
-const noop = (..._args: any[]): any => {};
-
-export const visualizationsServiceFactory: VisualizationsServiceFactory = () => ({
- showNewVisModal: noop,
- getByGroup: noop,
- getAliases: noop,
-});
diff --git a/x-pack/plugins/canvas/public/services/stubs/workpad.ts b/x-pack/plugins/canvas/public/services/stubs/workpad.ts
deleted file mode 100644
index 51be4e150ebe56..00000000000000
--- a/x-pack/plugins/canvas/public/services/stubs/workpad.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import moment from 'moment';
-import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
-
-// @ts-expect-error
-import { getDefaultWorkpad } from '../../state/defaults';
-import { CanvasWorkpadService } from '../workpad';
-import { CanvasTemplate, CanvasWorkpad } from '../../../types';
-
-type CanvasWorkpadServiceFactory = PluginServiceFactory;
-
-export const TIMEOUT = 500;
-export const promiseTimeout = (time: number) => () =>
- new Promise((resolve) => setTimeout(resolve, time));
-
-const DAY = 86400000;
-const JAN_1_2000 = 946684800000;
-
-const getWorkpads = (count = 3) => {
- const workpads = [];
- for (let i = 0; i < count; i++) {
- workpads[i] = {
- ...getDefaultWorkpad(),
- name: `Workpad ${i}`,
- id: `workpad-${i}`,
- '@created': moment(JAN_1_2000 + DAY * i).toDate(),
- '@timestamp': moment(JAN_1_2000 + DAY * (i + 1)).toDate(),
- };
- }
- return workpads;
-};
-
-export const getSomeWorkpads = (count = 3) => getWorkpads(count);
-
-export const findSomeWorkpads =
- (count = 3, timeout = TIMEOUT) =>
- (_term: string) => {
- return Promise.resolve()
- .then(promiseTimeout(timeout))
- .then(() => ({
- total: count,
- workpads: getSomeWorkpads(count),
- }));
- };
-
-const templates: CanvasTemplate[] = [
- {
- id: 'test1-id',
- name: 'test1',
- help: 'This is a test template',
- tags: ['tag1', 'tag2'],
- template_key: 'test1-key',
- },
- {
- id: 'test2-id',
- name: 'test2',
- help: 'This is a second test template',
- tags: ['tag2', 'tag3'],
- template_key: 'test2-key',
- },
-];
-
-export const findNoWorkpads =
- (timeout = TIMEOUT) =>
- (_term: string) => {
- return Promise.resolve()
- .then(promiseTimeout(timeout))
- .then(() => ({
- total: 0,
- workpads: [],
- }));
- };
-
-export const findSomeTemplates =
- (timeout = TIMEOUT) =>
- () => {
- return Promise.resolve()
- .then(promiseTimeout(timeout))
- .then(() => getSomeTemplates());
- };
-
-export const findNoTemplates =
- (timeout = TIMEOUT) =>
- () => {
- return Promise.resolve()
- .then(promiseTimeout(timeout))
- .then(() => getNoTemplates());
- };
-
-export const importWorkpad = (workpad: CanvasWorkpad) => Promise.resolve(workpad);
-export const getNoTemplates = () => ({ templates: [] });
-export const getSomeTemplates = () => ({ templates });
-
-export const workpadServiceFactory: CanvasWorkpadServiceFactory = () => ({
- get: (id: string) => Promise.resolve({ ...getDefaultWorkpad(), id }),
- resolve: (id: string) =>
- Promise.resolve({ outcome: 'exactMatch', workpad: { ...getDefaultWorkpad(), id } }),
- findTemplates: findNoTemplates(),
- create: (workpad) => Promise.resolve(workpad),
- import: (workpad) => importWorkpad(workpad),
- createFromTemplate: (_templateId: string) => Promise.resolve(getDefaultWorkpad()),
- find: findNoWorkpads(),
- remove: (_id: string) => Promise.resolve(),
- update: (id, workpad) => Promise.resolve(),
- updateWorkpad: (id, workpad) => Promise.resolve(),
- updateAssets: (id, assets) => Promise.resolve(),
- getRuntimeZip: (workpad) =>
- Promise.resolve(new Blob([JSON.stringify(workpad)], { type: 'application/json' })),
-});
diff --git a/x-pack/plugins/canvas/public/services/ui_actions.ts b/x-pack/plugins/canvas/public/services/ui_actions.ts
deleted file mode 100644
index 62f99facce023d..00000000000000
--- a/x-pack/plugins/canvas/public/services/ui_actions.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
-
-export interface CanvasUiActionsService {
- getTriggerCompatibleActions: UiActionsStart['getTriggerCompatibleActions'];
-}
diff --git a/x-pack/plugins/canvas/public/services/visualizations.ts b/x-pack/plugins/canvas/public/services/visualizations.ts
deleted file mode 100644
index dc816f764e5ec4..00000000000000
--- a/x-pack/plugins/canvas/public/services/visualizations.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { VisualizationsStart } from '@kbn/visualizations-plugin/public';
-
-export interface CanvasVisualizationsService {
- showNewVisModal: VisualizationsStart['showNewVisModal'];
- getByGroup: VisualizationsStart['getByGroup'];
- getAliases: VisualizationsStart['getAliases'];
-}
diff --git a/x-pack/plugins/canvas/public/services/workpad.ts b/x-pack/plugins/canvas/public/services/workpad.ts
deleted file mode 100644
index 72996f54c158d4..00000000000000
--- a/x-pack/plugins/canvas/public/services/workpad.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ResolvedSimpleSavedObject } from '@kbn/core/public';
-import { CanvasWorkpad, CanvasTemplate } from '../../types';
-import type { CanvasRenderedWorkpad } from '../../shareable_runtime/types';
-
-export type FoundWorkpads = Array>;
-export type FoundWorkpad = FoundWorkpads[number];
-export interface WorkpadFindResponse {
- total: number;
- workpads: FoundWorkpads;
-}
-
-export interface TemplateFindResponse {
- templates: CanvasTemplate[];
-}
-
-export interface ResolveWorkpadResponse {
- workpad: CanvasWorkpad;
- outcome: ResolvedSimpleSavedObject['outcome'];
- aliasId?: ResolvedSimpleSavedObject['alias_target_id'];
- aliasPurpose?: ResolvedSimpleSavedObject['alias_purpose'];
-}
-
-export interface CanvasWorkpadService {
- get: (id: string) => Promise;
- resolve: (id: string) => Promise;
- create: (workpad: CanvasWorkpad) => Promise;
- import: (workpad: CanvasWorkpad) => Promise;
- createFromTemplate: (templateId: string) => Promise;
- find: (term: string) => Promise;
- remove: (id: string) => Promise;
- findTemplates: () => Promise;
- update: (id: string, workpad: CanvasWorkpad) => Promise;
- updateWorkpad: (id: string, workpad: CanvasWorkpad) => Promise;
- updateAssets: (id: string, assets: CanvasWorkpad['assets']) => Promise;
- getRuntimeZip: (workpad: CanvasRenderedWorkpad) => Promise;
-}
diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js
index b94a198ee4be95..57b7da1ce1fef3 100644
--- a/x-pack/plugins/canvas/public/state/actions/elements.js
+++ b/x-pack/plugins/canvas/public/state/actions/elements.js
@@ -23,9 +23,11 @@ import { getValue as getResolvedArgsValue } from '../selectors/resolved_args';
import { getDefaultElement } from '../defaults';
import { ErrorStrings } from '../../../i18n';
import { subMultitree } from '../../lib/aeroelastic/functional';
-import { pluginServices } from '../../services';
+import { getCanvasExpressionService } from '../../services/canvas_expressions_service';
+import { getCanvasNotifyService } from '../../services/canvas_notify_service';
import { selectToplevelNodes } from './transient';
import * as args from './resolved_args';
+import { setFilter } from './filters';
const { actionsElements: strings } = ErrorStrings;
@@ -101,7 +103,7 @@ const fetchContextFn = ({ dispatch, getState }, index, element, fullRefresh = fa
const variables = getWorkpadVariablesAsObject(getState());
- const { expressions } = pluginServices.getServices();
+ const expressions = getCanvasExpressionService();
const elementWithNewAst = set(element, pathToTarget, astChain);
// get context data from a partial AST
@@ -129,7 +131,8 @@ const fetchRenderableWithContextFn = ({ dispatch, getState }, element, ast, cont
});
const variables = getWorkpadVariablesAsObject(getState());
- const { expressions, notify } = pluginServices.getServices();
+ const expressions = getCanvasExpressionService();
+ const notify = getCanvasNotifyService();
return expressions
.runInterpreter(ast, context, variables, { castToRender: true })
@@ -177,7 +180,8 @@ export const fetchAllRenderables = createThunk(
const argumentPath = [element.id, 'expressionRenderable'];
const variables = getWorkpadVariablesAsObject(getState());
- const { expressions, notify } = pluginServices.getServices();
+ const expressions = getCanvasExpressionService();
+ const notify = getCanvasNotifyService();
return expressions
.runInterpreter(ast, null, variables, { castToRender: true })
@@ -260,18 +264,6 @@ export const removeElements = createThunk(
}
);
-export const setFilter = createThunk(
- 'setFilter',
- ({ dispatch }, filter, elementId, doRender = true) => {
- const _setFilter = createAction('setFilter');
- dispatch(_setFilter({ filter, elementId }));
-
- if (doRender === true) {
- dispatch(fetchAllRenderables());
- }
- }
-);
-
export const setExpression = createThunk('setExpression', setExpressionFn);
function setExpressionFn({ dispatch, getState }, expression, elementId, pageId, doRender = true) {
// dispatch action to update the element in state
@@ -290,7 +282,11 @@ function setExpressionFn({ dispatch, getState }, expression, elementId, pageId,
)
) {
const filter = '';
- dispatch(setFilter(filter, elementId, pageId, doRender));
+ dispatch(setFilter(filter, elementId, pageId));
+
+ if (doRender) {
+ dispatch(fetchAllRenderables(updatedElement));
+ }
// setFilter will trigger a re-render so we can skip the fetch here
} else if (doRender === true) {
dispatch(fetchRenderable(updatedElement));
@@ -302,7 +298,7 @@ const setAst = createThunk('setAst', ({ dispatch }, ast, element, pageId, doRend
const expression = toExpression(ast);
dispatch(setExpression(expression, element.id, pageId, doRender));
} catch (err) {
- const notifyService = pluginServices.getServices().notify;
+ const notifyService = getCanvasNotifyService();
notifyService.error(err);
// TODO: remove this, may have been added just to cause a re-render, but why?
diff --git a/x-pack/plugins/canvas/public/state/actions/filters.js b/x-pack/plugins/canvas/public/state/actions/filters.js
new file mode 100644
index 00000000000000..3347925dc5a15e
--- /dev/null
+++ b/x-pack/plugins/canvas/public/state/actions/filters.js
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { createAction } from 'redux-actions';
+import { createThunk } from '../../lib/create_thunk';
+
+export const setFilter = createThunk('setFilter', ({ dispatch }, filter, elementId) => {
+ const _setFilter = createAction('setFilter');
+ dispatch(_setFilter({ filter, elementId }));
+});
diff --git a/x-pack/plugins/canvas/public/state/initial_state.js b/x-pack/plugins/canvas/public/state/initial_state.js
index 88330c92d867b7..fd509c952ef187 100644
--- a/x-pack/plugins/canvas/public/state/initial_state.js
+++ b/x-pack/plugins/canvas/public/state/initial_state.js
@@ -6,18 +6,15 @@
*/
import { get } from 'lodash';
-import { pluginServices } from '../services';
+import { coreServices } from '../services/kibana_services';
import { getDefaultWorkpad, getDefaultSidebar, getDefaultFlyouts } from './defaults';
export const getInitialState = (path) => {
- const platformService = pluginServices.getServices().platform;
- const { getHasWriteAccess } = platformService;
-
const state = {
app: {}, // Kibana stuff in here
assets: {}, // assets end up here
transient: {
- canUserWrite: getHasWriteAccess(),
+ canUserWrite: coreServices.application.capabilities.canvas.save,
zoomScale: 1,
elementStats: {
total: 0,
diff --git a/x-pack/plugins/canvas/public/state/reducers/elements.js b/x-pack/plugins/canvas/public/state/reducers/elements.js
index 2be1cf519ee133..fdb15a56212224 100644
--- a/x-pack/plugins/canvas/public/state/reducers/elements.js
+++ b/x-pack/plugins/canvas/public/state/reducers/elements.js
@@ -8,7 +8,6 @@
import { handleActions } from 'redux-actions';
import immutable from 'object-path-immutable';
import { get } from 'lodash';
-import * as actions from '../actions/elements';
const { assign, push, del, set } = immutable;
@@ -102,17 +101,16 @@ const getPageWithElementId = (workpad, elementId) => {
export const elementsReducer = handleActions(
{
- // TODO: This takes the entire element, which is not necessary, it could just take the id.
- [actions.setExpression]: (workpadState, { payload }) => {
+ ['setExpression']: (workpadState, { payload }) => {
const { expression, pageId, elementId } = payload;
return assignNodeProperties(workpadState, pageId, elementId, { expression });
},
- [actions.setFilter]: (workpadState, { payload }) => {
+ ['setFilter']: (workpadState, { payload }) => {
const { filter, elementId } = payload;
const pageId = getPageWithElementId(workpadState, elementId);
return assignNodeProperties(workpadState, pageId, elementId, { filter });
},
- [actions.setMultiplePositions]: (workpadState, { payload }) =>
+ ['setMultiplePositions']: (workpadState, { payload }) =>
payload.repositionedElements.reduce(
(previousWorkpadState, { position, pageId, elementId }) =>
assignNodeProperties(previousWorkpadState, pageId, elementId, {
@@ -120,11 +118,11 @@ export const elementsReducer = handleActions(
}),
workpadState
),
- [actions.elementLayer]: (workpadState, { payload: { pageId, elementId, movement } }) => {
+ ['elementLayer']: (workpadState, { payload: { pageId, elementId, movement } }) => {
const location = getLocationFromIds(workpadState, pageId, elementId);
return moveNodeLayer(workpadState, pageId, elementId, movement, location);
},
- [actions.addElement]: (workpadState, { payload: { pageId, element } }) => {
+ ['addElement']: (workpadState, { payload: { pageId, element } }) => {
const pageIndex = getPageIndexById(workpadState, pageId);
if (pageIndex < 0) {
return workpadState;
@@ -143,7 +141,7 @@ export const elementsReducer = handleActions(
trimElement(element)
);
},
- [actions.insertNodes]: (workpadState, { payload: { pageId, elements } }) => {
+ ['insertNodes']: (workpadState, { payload: { pageId, elements } }) => {
const pageIndex = getPageIndexById(workpadState, pageId);
if (pageIndex < 0) {
return workpadState;
@@ -158,7 +156,7 @@ export const elementsReducer = handleActions(
workpadState
);
},
- [actions.removeElements]: (workpadState, { payload: { pageId, elementIds } }) => {
+ ['removeElements']: (workpadState, { payload: { pageId, elementIds } }) => {
const pageIndex = getPageIndexById(workpadState, pageId);
if (pageIndex < 0) {
return workpadState;
diff --git a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts
index 5b963e2c80c7e3..8f79ed529cd968 100644
--- a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts
+++ b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts
@@ -9,10 +9,7 @@ import { fromExpression, toExpression } from '@kbn/interpreter';
import { handleActions } from 'redux-actions';
import { State } from '../../../types';
-import {
- UpdateEmbeddableExpressionActionType,
- UpdateEmbeddableExpressionPayload,
-} from '../actions/embeddable';
+import { UpdateEmbeddableExpressionPayload } from '../actions/embeddable';
// @ts-expect-error untyped local
import { assignNodeProperties } from './elements';
@@ -22,7 +19,7 @@ export const embeddableReducer = handleActions<
UpdateEmbeddableExpressionPayload
>(
{
- [UpdateEmbeddableExpressionActionType]: (workpadState, { payload }) => {
+ ['updateEmbeddableExpression']: (workpadState, { payload }) => {
if (!payload) {
return workpadState;
}
diff --git a/x-pack/plugins/canvas/public/state/reducers/resolved_args.js b/x-pack/plugins/canvas/public/state/reducers/resolved_args.js
index 3514036cafc0df..3652a8d462fb82 100644
--- a/x-pack/plugins/canvas/public/state/reducers/resolved_args.js
+++ b/x-pack/plugins/canvas/public/state/reducers/resolved_args.js
@@ -10,8 +10,6 @@ import immutable from 'object-path-immutable';
import { get } from 'lodash';
import { prepend } from '../../lib/modify_path';
import * as actions from '../actions/resolved_args';
-import { flushContext, flushContextAfterIndex } from '../actions/elements';
-import { setWorkpad } from '../actions/workpad';
const { set, del } = immutable;
/*
@@ -114,14 +112,14 @@ export const resolvedArgsReducer = handleActions(
/*
* Flush all cached contexts
*/
- [flushContext]: (transientState, { payload: elementId }) => {
+ ['flushContext']: (transientState, { payload: elementId }) => {
return del(transientState, getFullPath([elementId, 'expressionContext']));
},
/*
* Flush cached context indices from the given index to the last
*/
- [flushContextAfterIndex]: (transientState, { payload }) => {
+ ['flushContextAfterIndex']: (transientState, { payload }) => {
const { elementId, index } = payload;
const expressionContext = get(transientState, getFullPath([elementId, 'expressionContext']));
@@ -139,7 +137,7 @@ export const resolvedArgsReducer = handleActions(
return state;
}, transientState);
},
- [setWorkpad]: (transientState, {}) => {
+ ['setWorkpad']: (transientState, {}) => {
return set(transientState, 'resolvedArgs', {});
},
},
diff --git a/x-pack/plugins/canvas/public/state/reducers/transient.js b/x-pack/plugins/canvas/public/state/reducers/transient.js
index e41ab5e4822a97..2f0e7bd66126c0 100644
--- a/x-pack/plugins/canvas/public/state/reducers/transient.js
+++ b/x-pack/plugins/canvas/public/state/reducers/transient.js
@@ -7,11 +7,6 @@
import { handleActions } from 'redux-actions';
import immutable from 'object-path-immutable';
-import { restoreHistory } from '../actions/history';
-import * as pageActions from '../actions/pages';
-import * as transientActions from '../actions/transient';
-import { removeElements } from '../actions/elements';
-import { setRefreshInterval, enableAutoplay, setAutoplayInterval } from '../actions/workpad';
const { set, del } = immutable;
@@ -19,9 +14,9 @@ export const transientReducer = handleActions(
{
// clear all the resolved args when restoring the history
// TODO: we shouldn't need to reset the resolved args for history
- [restoreHistory]: (transientState) => set(transientState, 'resolvedArgs', {}),
+ ['restoreHistory']: (transientState) => set(transientState, 'resolvedArgs', {}),
- [removeElements]: (transientState, { payload: { elementIds } }) => {
+ ['removeElements']: (transientState, { payload: { elementIds } }) => {
const { selectedToplevelNodes } = transientState;
return del(
{
@@ -32,53 +27,53 @@ export const transientReducer = handleActions(
);
},
- [transientActions.setFirstLoad]: (transientState, { payload }) => {
+ ['setFirstLoad']: (transientState, { payload }) => {
return set(transientState, 'isFirstLoad', Boolean(payload));
},
- [transientActions.setFullscreen]: (transientState, { payload }) => {
+ ['setFullscreen']: (transientState, { payload }) => {
return set(transientState, 'fullscreen', Boolean(payload));
},
- [transientActions.setElementStats]: (transientState, { payload }) => {
+ ['setElementStats']: (transientState, { payload }) => {
return set(transientState, 'elementStats', payload);
},
- [transientActions.selectToplevelNodes]: (transientState, { payload }) => {
+ ['selectToplevelNodes']: (transientState, { payload }) => {
return {
...transientState,
selectedToplevelNodes: payload,
};
},
- [transientActions.setZoomScale]: (transientState, { payload }) => {
+ ['setZoomScale']: (transientState, { payload }) => {
return {
...transientState,
zoomScale: payload || 1,
};
},
- [pageActions.setPage]: (transientState) => {
+ ['setPage']: (transientState) => {
return { ...transientState, selectedToplevelNodes: [] };
},
- [pageActions.addPage]: (transientState) => {
+ ['addPage']: (transientState) => {
return { ...transientState, selectedToplevelNodes: [] };
},
- [pageActions.duplicatePage]: (transientState) => {
+ ['duplicatePage']: (transientState) => {
return { ...transientState, selectedToplevelNodes: [] };
},
- [setRefreshInterval]: (transientState, { payload }) => {
+ ['setRefreshInterval']: (transientState, { payload }) => {
return { ...transientState, refresh: { interval: Number(payload) || 0 } };
},
- [enableAutoplay]: (transientState, { payload }) => {
+ ['enableAutoplay']: (transientState, { payload }) => {
return set(transientState, 'autoplay.enabled', Boolean(payload) || false);
},
- [setAutoplayInterval]: (transientState, { payload }) => {
+ ['setAutoplayInterval']: (transientState, { payload }) => {
return set(transientState, 'autoplay.interval', Number(payload) || 0);
},
},
diff --git a/x-pack/plugins/canvas/public/state/reducers/workpad.js b/x-pack/plugins/canvas/public/state/reducers/workpad.js
index ebde0106f9c012..9c337f965ae3aa 100644
--- a/x-pack/plugins/canvas/public/state/reducers/workpad.js
+++ b/x-pack/plugins/canvas/public/state/reducers/workpad.js
@@ -6,66 +6,52 @@
*/
import { handleActions } from 'redux-actions';
-import { pluginServices } from '../../services';
+import { coreServices } from '../../services/kibana_services';
import { getDefaultWorkpad } from '../defaults';
-import {
- setWorkpad,
- sizeWorkpad,
- setColors,
- setName,
- setWriteable,
- setWorkpadCSS,
- setWorkpadVariables,
- resetWorkpad,
-} from '../actions/workpad';
import { APP_ROUTE_WORKPAD } from '../../../common/lib/constants';
export const workpadReducer = handleActions(
{
- [setWorkpad]: (workpadState, { payload }) => {
- pluginServices
- .getServices()
- .platform.setRecentlyAccessed(
- `${APP_ROUTE_WORKPAD}/${payload.id}`,
- payload.name,
- payload.id
- );
+ ['setWorkpad']: (workpadState, { payload }) => {
+ coreServices.chrome.recentlyAccessed.add(
+ `${APP_ROUTE_WORKPAD}/${payload.id}`,
+ payload.name,
+ payload.id
+ );
return payload;
},
- [sizeWorkpad]: (workpadState, { payload }) => {
+ ['sizeWorkpad']: (workpadState, { payload }) => {
return { ...workpadState, ...payload };
},
- [setColors]: (workpadState, { payload }) => {
+ ['setColors']: (workpadState, { payload }) => {
return { ...workpadState, colors: payload };
},
- [setName]: (workpadState, { payload }) => {
- pluginServices
- .getServices()
- .platform.setRecentlyAccessed(
- `${APP_ROUTE_WORKPAD}/${workpadState.id}`,
- payload,
- workpadState.id
- );
+ ['setName']: (workpadState, { payload }) => {
+ coreServices.chrome.recentlyAccessed.add(
+ `${APP_ROUTE_WORKPAD}/${workpadState.id}`,
+ payload,
+ workpadState.id
+ );
return { ...workpadState, name: payload };
},
- [setWriteable]: (workpadState, { payload }) => {
+ ['setWriteable']: (workpadState, { payload }) => {
return { ...workpadState, isWriteable: Boolean(payload) };
},
- [setWorkpadCSS]: (workpadState, { payload }) => {
+ ['setWorkpadCSS']: (workpadState, { payload }) => {
return { ...workpadState, css: payload };
},
- [setWorkpadVariables]: (workpadState, { payload }) => {
+ ['setWorkpadVariables']: (workpadState, { payload }) => {
return { ...workpadState, variables: payload };
},
- [resetWorkpad]: () => ({ ...getDefaultWorkpad() }),
+ ['resetWorkpad']: () => ({ ...getDefaultWorkpad() }),
},
{}
);
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap
index 010847e63a023a..4aa379aa194bc5 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap
+++ b/x-pack/plugins/canvas/shareable_runtime/components/__snapshots__/app.test.tsx.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[` App renders properly 1`] = `"There is a new region landmark with page level controls at the end of the document.
"`;
+exports[` App renders properly 1`] = `"There is a new region landmark with page level controls at the end of the document.
"`;
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx
index ec4ea09e8fcc7d..30d55c8531b1dd 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/app.test.tsx
@@ -13,6 +13,7 @@
import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
+
// import { act } from 'react-dom/test-utils';
import { App } from './app';
import { sharedWorkpads, WorkpadNames, tick } from '../test';
@@ -34,17 +35,14 @@ import { openSettings, selectMenuItem } from '../test/interactions';
// Mock the renderers
jest.mock('../supported_renderers');
+// @ts-ignore Importing this to mock
+import * as Portal from '@elastic/eui/lib/components/portal/portal';
+
// Mock the EuiPortal - `insertAdjacentElement is not supported in
// `jsdom` 12. We're just going to render a `div` with the children
// so the `enzyme` tests will be accurate.
-jest.mock('@elastic/eui/lib/components/portal/portal', () => {
- // Local constants are not supported in Jest mocks-- they must be
- // imported within the mock.
- // eslint-disable-next-line @typescript-eslint/no-shadow
- const React = jest.requireActual('react');
- return {
- EuiPortal: (props: any) => {props.children}
,
- };
+jest.spyOn(Portal, 'EuiPortal').mockImplementation((props: any) => {
+ return {props.children}
;
});
const getWrapper: (name?: WorkpadNames) => ReactWrapper = (name = 'hello') => {
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
index bf66b5df800ecc..cfe41427b1ea1e 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
@@ -1,7 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` can navigate Autoplay Settings 1`] = `
-
+
@@ -94,7 +96,9 @@ exports[`
can navigate Autoplay Settings 1`] = `
`;
exports[`
can navigate Autoplay Settings 2`] = `
-
+
@@ -338,7 +342,9 @@ exports[`
can navigate Autoplay Settings 2`] = `
`;
exports[`
can navigate Toolbar Settings, closes when activated 1`] = `
-
+
@@ -431,7 +437,9 @@ exports[`
can navigate Toolbar Settings, closes when activated 1`] =
`;
exports[`
can navigate Toolbar Settings, closes when activated 2`] = `
-
+
@@ -606,7 +614,9 @@ exports[`
can navigate Toolbar Settings, closes when activated 2`] =
`;
exports[`
can navigate Toolbar Settings, closes when activated 3`] = `
-
+
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.test.tsx
index cc275dd983e3ce..0ab9e13e367651 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.test.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.test.tsx
@@ -21,12 +21,14 @@ import { Settings } from './settings';
jest.mock('../../../supported_renderers');
-jest.mock('@elastic/eui/lib/components/portal/portal', () => {
- // eslint-disable-next-line @typescript-eslint/no-shadow
- const React = jest.requireActual('react');
- return {
- EuiPortal: (props: any) =>
{props.children}
,
- };
+// @ts-ignore Importing this to mock
+import * as Portal from '@elastic/eui/lib/components/portal/portal';
+
+// Mock the EuiPortal - `insertAdjacentElement is not supported in
+// `jsdom` 12. We're just going to render a `div` with the children
+// so the `enzyme` tests will be accurate.
+jest.spyOn(Portal, 'EuiPortal').mockImplementation((props: any) => {
+ return
{props.children}
;
});
describe('
', () => {
diff --git a/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts b/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts
index bcfe94adf94be1..f4d4f8ac442d7e 100644
--- a/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts
+++ b/x-pack/plugins/canvas/shareable_runtime/test/selectors.ts
@@ -42,7 +42,7 @@ export const getSettingsTrigger = (wrapper: ReactWrapper) =>
export const getPopover = (wrapper: ReactWrapper) => wrapper.find('EuiPopover');
-export const getPortal = (wrapper: ReactWrapper) => wrapper.find('EuiPortal');
+export const getPortal = (wrapper: ReactWrapper) => wrapper.find('.mockedEuiPortal');
export const getContextMenu = (wrapper: ReactWrapper) => wrapper.find('EuiContextMenuClass');
diff --git a/x-pack/plugins/canvas/storybook/constants.ts b/x-pack/plugins/canvas/storybook/constants.ts
index 711b9391794522..4f0ad2a602da0f 100644
--- a/x-pack/plugins/canvas/storybook/constants.ts
+++ b/x-pack/plugins/canvas/storybook/constants.ts
@@ -8,3 +8,34 @@
import path from 'path';
export const KIBANA_ROOT = path.resolve(__dirname, '../../../..');
+
+export const argTypes = {
+ hasTemplates: {
+ name: 'Has templates?',
+ type: {
+ name: 'boolean',
+ },
+ defaultValue: true,
+ control: {
+ type: 'boolean',
+ },
+ },
+ useStaticData: {
+ name: 'Use static data?',
+ type: {
+ name: 'boolean',
+ },
+ defaultValue: false,
+ control: {
+ type: 'boolean',
+ },
+ },
+ workpadCount: {
+ name: 'Number of workpads',
+ type: { name: 'number' },
+ defaultValue: 5,
+ control: {
+ type: 'range',
+ },
+ },
+};
diff --git a/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx b/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
index b5bb0710c3de29..c99f5df184cd4e 100644
--- a/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
+++ b/x-pack/plugins/canvas/storybook/decorators/services_decorator.tsx
@@ -10,19 +10,10 @@ import React from 'react';
import { DecoratorFn } from '@storybook/react';
import { I18nProvider } from '@kbn/i18n-react';
-import { PluginServiceRegistry } from '@kbn/presentation-util-plugin/public';
-import { pluginServices, CanvasPluginServices } from '../../public/services';
-import { pluginServiceProviders, StorybookParams } from '../../public/services/storybook';
import { LegacyServicesProvider } from '../../public/services/legacy';
-import { startServices } from '../../public/services/legacy/stubs';
+import { setStubKibanaServices } from '../../public/services/mocks';
export const servicesContextDecorator = (): DecoratorFn => {
- const pluginServiceRegistry = new PluginServiceRegistry
(
- pluginServiceProviders
- );
-
- pluginServices.setRegistry(pluginServiceRegistry.start({}));
-
return (story: Function, storybook) => {
if (process.env.JEST_WORKER_ID !== undefined) {
storybook.args.useStaticData = true;
@@ -33,6 +24,7 @@ export const servicesContextDecorator = (): DecoratorFn => {
};
export const legacyContextDecorator = () => {
- startServices();
+ setStubKibanaServices();
+
return (story: Function) => {story()};
};
diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json
index 85121b72c868c3..251deca87c4ee5 100644
--- a/x-pack/plugins/canvas/tsconfig.json
+++ b/x-pack/plugins/canvas/tsconfig.json
@@ -12,6 +12,7 @@
"allowJs": false
},
"include": [
+ "*.ts",
"../../../typings/**/*",
"__fixtures__/**/*",
"canvas_plugin_src/**/*",
@@ -88,9 +89,7 @@
"@kbn/presentation-containers",
"@kbn/presentation-publishing",
"@kbn/react-kibana-context-render",
- "@kbn/search-types",
+ "@kbn/search-types"
],
- "exclude": [
- "target/**/*",
- ]
+ "exclude": ["target/**/*"]
}
From 445a2bd5baed49a7e07ab3e6c1b86bc1e5821496 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 8 Oct 2024 21:39:29 +0100
Subject: [PATCH 49/50] skip flaky suite (#187182)
---
x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts
index d84d3aa691d1e6..ca0692b310606d 100644
--- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts
+++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts
@@ -84,7 +84,8 @@ describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () =>
});
});
- describe('Case', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/187182
+ describe.skip('Case', () => {
let caseId: string;
beforeEach(() => {
From 2dfd8f1bedaa01453f8d1fbd4e6375cf1bca2843 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 8 Oct 2024 21:47:25 +0100
Subject: [PATCH 50/50] skip flaky suite (#170371)
---
.../response_actions/response_console/process_operations.cy.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts
index 9484122c013d7f..ed05f5a26e356d 100644
--- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts
+++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/process_operations.cy.ts
@@ -27,6 +27,7 @@ import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_
const AGENT_BEAT_FILE_PATH_SUFFIX = '/components/agentbeat';
// FLAKY: https://github.com/elastic/kibana/issues/170370
+// FLAKY: https://github.com/elastic/kibana/issues/170371
describe.skip('Response console', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => {
beforeEach(() => {
login();